mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 15:00:30 +01:00
Move mining fee payment to taker. Refactor package io.bitsquare.pricefeed to io.bitsquare.provider.price. Rename option priceFeedProviders to providers
This commit is contained in:
parent
d5817020a6
commit
963086106f
65 changed files with 636 additions and 281 deletions
|
@ -2,5 +2,5 @@ package io.bitsquare.app;
|
|||
|
||||
public class DevFlags {
|
||||
public static final boolean STRESS_TEST_MODE = false;
|
||||
public static final boolean DEV_MODE = STRESS_TEST_MODE || true;
|
||||
public static final boolean DEV_MODE = STRESS_TEST_MODE || false;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ public class AppOptionKeys {
|
|||
public static final String APP_NAME_KEY = "appName";
|
||||
public static final String APP_DATA_DIR_KEY = "appDataDir";
|
||||
public static final String MAX_MEMORY = "maxMemory";
|
||||
public static final String PRICE_FEED_PROVIDERS = "priceFeedProviders";
|
||||
public static final String PROVIDERS = "providers";
|
||||
public static final String BTC_NODES = "btcNodes";
|
||||
public static final String USE_TOR_FOR_BTC = "useTorForBtc";
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ public class BitsquareEnvironment extends StandardEnvironment {
|
|||
private final String userDataDir;
|
||||
private final String appDataDir;
|
||||
private final String btcNetworkDir;
|
||||
private final String logLevel, priceFeedProviders;
|
||||
private final String logLevel, providers;
|
||||
private BitcoinNetwork bitcoinNetwork;
|
||||
private final String btcNodes, seedNodes, ignoreDevMsg, useTorForBtc,
|
||||
myAddress, banList, dumpStatistics, maxMemory, socks5ProxyBtcAddress, socks5ProxyHttpAddress;
|
||||
|
@ -145,8 +145,8 @@ public class BitsquareEnvironment extends StandardEnvironment {
|
|||
maxMemory = commandLineProperties.containsProperty(AppOptionKeys.MAX_MEMORY) ?
|
||||
(String) commandLineProperties.getProperty(AppOptionKeys.MAX_MEMORY) :
|
||||
"";
|
||||
priceFeedProviders = commandLineProperties.containsProperty(AppOptionKeys.PRICE_FEED_PROVIDERS) ?
|
||||
(String) commandLineProperties.getProperty(AppOptionKeys.PRICE_FEED_PROVIDERS) :
|
||||
providers = commandLineProperties.containsProperty(AppOptionKeys.PROVIDERS) ?
|
||||
(String) commandLineProperties.getProperty(AppOptionKeys.PROVIDERS) :
|
||||
"";
|
||||
|
||||
seedNodes = commandLineProperties.containsProperty(NetworkOptionKeys.SEED_NODES_KEY) ?
|
||||
|
@ -244,7 +244,7 @@ public class BitsquareEnvironment extends StandardEnvironment {
|
|||
setProperty(AppOptionKeys.APP_NAME_KEY, appName);
|
||||
setProperty(AppOptionKeys.MAX_MEMORY, maxMemory);
|
||||
setProperty(AppOptionKeys.USER_DATA_DIR_KEY, userDataDir);
|
||||
setProperty(AppOptionKeys.PRICE_FEED_PROVIDERS, priceFeedProviders);
|
||||
setProperty(AppOptionKeys.PROVIDERS, providers);
|
||||
|
||||
setProperty(AppOptionKeys.BTC_NODES, btcNodes);
|
||||
setProperty(AppOptionKeys.USE_TOR_FOR_BTC, useTorForBtc);
|
||||
|
|
|
@ -109,7 +109,7 @@ public abstract class BitsquareExecutable {
|
|||
parser.accepts(AppOptionKeys.DUMP_STATISTICS, description("If set to true the trade statistics are stored as json file in the data dir.", false))
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
parser.accepts(AppOptionKeys.PRICE_FEED_PROVIDERS, description("Custom price feed providers (comma separated)", false))
|
||||
parser.accepts(AppOptionKeys.PROVIDERS, description("Custom providers (comma separated)", false))
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(BtcOptionKeys.BTC_NETWORK, description("Bitcoin network", BitcoinNetwork.DEFAULT))
|
||||
|
|
|
@ -21,10 +21,9 @@ import com.google.inject.Singleton;
|
|||
import io.bitsquare.app.AppModule;
|
||||
import io.bitsquare.app.AppOptionKeys;
|
||||
import io.bitsquare.btc.blockchain.BlockchainService;
|
||||
import io.bitsquare.btc.blockchain.providers.BlockTrailProvider;
|
||||
import io.bitsquare.btc.blockchain.providers.BlockrIOProvider;
|
||||
import io.bitsquare.btc.blockchain.providers.TradeBlockProvider;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.http.HttpClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
@ -54,7 +53,7 @@ public class BitcoinModule extends AppModule {
|
|||
|
||||
bindConstant().annotatedWith(named(AppOptionKeys.BTC_NODES)).to(env.getRequiredProperty(AppOptionKeys.BTC_NODES));
|
||||
bindConstant().annotatedWith(named(AppOptionKeys.USE_TOR_FOR_BTC)).to(env.getRequiredProperty(AppOptionKeys.USE_TOR_FOR_BTC));
|
||||
bindConstant().annotatedWith(named(AppOptionKeys.PRICE_FEED_PROVIDERS)).to(env.getRequiredProperty(AppOptionKeys.PRICE_FEED_PROVIDERS));
|
||||
bindConstant().annotatedWith(named(AppOptionKeys.PROVIDERS)).to(env.getRequiredProperty(AppOptionKeys.PROVIDERS));
|
||||
|
||||
bind(AddressEntryList.class).in(Singleton.class);
|
||||
bind(TradeWalletService.class).in(Singleton.class);
|
||||
|
@ -63,11 +62,8 @@ public class BitcoinModule extends AppModule {
|
|||
|
||||
bind(PriceFeedService.class).in(Singleton.class);
|
||||
|
||||
bind(BlockrIOProvider.class).in(Singleton.class);
|
||||
bind(BlockTrailProvider.class).in(Singleton.class);
|
||||
bind(TradeBlockProvider.class).in(Singleton.class);
|
||||
|
||||
bind(FeeService.class).in(Singleton.class);
|
||||
bind(HttpClient.class).in(Singleton.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ public class FeePolicy {
|
|||
// http://www.cointape.com
|
||||
// http://p2sh.info/dashboard/db/fee-estimation
|
||||
// https://bitcoinfees.github.io/#1d
|
||||
// https://estimatefee.appspot.com/
|
||||
// Average values are 10-100 satoshis/byte in january 2016
|
||||
//
|
||||
// Our trade transactions have a fixed set of inputs and outputs making the size very predictable
|
||||
|
@ -49,7 +50,7 @@ public class FeePolicy {
|
|||
// The BitcoinJ fee calculation use kb so a tx size < 1kb will still pay the fee for a kb tx.
|
||||
// Our payout tx has about 370 bytes so we get a fee/kb value of about 90 satoshi/byte making it high priority
|
||||
// Other payout transactions (E.g. arbitrators many collected transactions) will go with 30 satoshi/byte if > 1kb
|
||||
private static Coin NON_TRADE_FEE_PER_KB = DevFlags.STRESS_TEST_MODE ? Coin.valueOf(5_000) : Coin.valueOf(20_000); // 0.0002 BTC about 0.08 EUR @ 400 EUR/BTC
|
||||
private static Coin NON_TRADE_FEE_PER_KB = Coin.valueOf(40_000); // 0.0004 BTC about 0.16 EUR @ 400 EUR/BTC
|
||||
|
||||
public static void setNonTradeFeePerKb(Coin nonTradeFeePerKb) {
|
||||
NON_TRADE_FEE_PER_KB = nonTradeFeePerKb;
|
||||
|
@ -59,18 +60,6 @@ public class FeePolicy {
|
|||
return NON_TRADE_FEE_PER_KB;
|
||||
}
|
||||
|
||||
// 0.0005 BTC 0.05% of 1 BTC about 0.2 EUR @ 400 EUR/BTC
|
||||
public static Coin getCreateOfferFee() {
|
||||
// We need to pay the quite high miner fee of 30_000 from the trading fee tx so 30_000 us our lower limit
|
||||
// The arbitrator receive only 0.0002 BTC - less than the miners
|
||||
return DevFlags.STRESS_TEST_MODE ? Coin.valueOf(10_000) : Coin.valueOf(50_000);
|
||||
}
|
||||
|
||||
// 0.001 BTC 0.1% of 1 BTC about 0.4 EUR @ 400 EUR/BTC
|
||||
public static Coin getTakeOfferFee() {
|
||||
return DevFlags.STRESS_TEST_MODE ? Coin.valueOf(10_000) : Coin.valueOf(100_000);
|
||||
}
|
||||
|
||||
|
||||
// TODO will be increased once we get higher limits
|
||||
// 0.01 BTC; about 4 EUR @ 400 EUR/BTC
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.btc;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FeeService {
|
||||
private static final Logger log = LoggerFactory.getLogger(FeeService.class);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public FeeService() {
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
|
||||
public Coin getTxFee() {
|
||||
counter += 100;
|
||||
// log.error("getTxFee " + (20_000 + counter));
|
||||
//return Coin.valueOf(20_000 + counter);
|
||||
return Coin.valueOf(20_000);
|
||||
}
|
||||
|
||||
public Coin getTxFeeForWithdrawal() {
|
||||
return Coin.valueOf(20_000);
|
||||
}
|
||||
|
||||
public Coin getCreateOfferFee() {
|
||||
return Coin.valueOf(50_000);
|
||||
}
|
||||
|
||||
public Coin getTakeOfferFee() {
|
||||
return Coin.valueOf(100_000);
|
||||
}
|
||||
|
||||
}
|
|
@ -145,10 +145,7 @@ public class TradeWalletService {
|
|||
boolean useSavingsWallet, Coin tradingFee, Coin txFee, String feeReceiverAddresses)
|
||||
throws InsufficientMoneyException, AddressFormatException {
|
||||
Transaction tradingFeeTx = new Transaction(params);
|
||||
Preconditions.checkArgument(Restrictions.isAboveFixedTxFeeForTradesAndDust(tradingFee, txFee),
|
||||
"You cannot send an amount which are smaller than the fee + dust output.");
|
||||
Coin outPutAmount = tradingFee.subtract(txFee);
|
||||
tradingFeeTx.addOutput(outPutAmount, new Address(params, feeReceiverAddresses));
|
||||
tradingFeeTx.addOutput(tradingFee, new Address(params, feeReceiverAddresses));
|
||||
// the reserved amount we need for the trade we send to our trade reservedForTradeAddress
|
||||
tradingFeeTx.addOutput(reservedFundsForOffer, reservedForTradeAddress);
|
||||
|
||||
|
@ -1123,12 +1120,12 @@ public class TradeWalletService {
|
|||
}
|
||||
|
||||
private static void printTxWithInputs(String tracePrefix, Transaction tx) {
|
||||
log.trace(tracePrefix + ": " + tx.toString());
|
||||
log.info(tracePrefix + ": " + tx.toString());
|
||||
for (TransactionInput input : tx.getInputs()) {
|
||||
if (input.getConnectedOutput() != null)
|
||||
log.trace(tracePrefix + " input value: " + input.getConnectedOutput().getValue().toFriendlyString());
|
||||
log.info(tracePrefix + " input value: " + input.getConnectedOutput().getValue().toFriendlyString());
|
||||
else
|
||||
log.trace(tracePrefix + ": Transaction already has inputs but we don't have the connected outputs, so we don't know the value.");
|
||||
log.info(tracePrefix + ": Transaction already has inputs but we don't have the connected outputs, so we don't know the value.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,14 @@
|
|||
package io.bitsquare.btc.blockchain;
|
||||
|
||||
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.blockchain.providers.BlockTrailProvider;
|
||||
import io.bitsquare.btc.blockchain.providers.BlockchainTxProvider;
|
||||
import io.bitsquare.btc.blockchain.providers.BlockrIOProvider;
|
||||
import io.bitsquare.btc.blockchain.providers.TradeBlockProvider;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class BlockchainService {
|
||||
private static final Logger log = LoggerFactory.getLogger(BlockchainService.class);
|
||||
|
||||
private final ArrayList<BlockchainTxProvider> blockchainTxProviders;
|
||||
// Once needed will be moved to Providers so we stick with out hidden service routing
|
||||
|
||||
/*private final ArrayList<BlockchainTxProvider> blockchainTxProviders;
|
||||
|
||||
@Inject
|
||||
public BlockchainService(BlockrIOProvider blockrIOProvider, BlockTrailProvider blockTrailProvider, TradeBlockProvider tradeBlockProvider) {
|
||||
|
@ -33,8 +21,8 @@ public class BlockchainService {
|
|||
final SettableFuture<Coin> resultFuture = SettableFuture.create();
|
||||
|
||||
for (BlockchainTxProvider provider : blockchainTxProviders) {
|
||||
GetFeeRequest getFeeRequest = new GetFeeRequest();
|
||||
SettableFuture<Coin> future = getFeeRequest.request(transactionId, provider);
|
||||
GetTransactionRequest getTransactionRequest = new GetTransactionRequest();
|
||||
SettableFuture<Coin> future = getTransactionRequest.request(transactionId, provider);
|
||||
Futures.addCallback(future, new FutureCallback<Coin>() {
|
||||
public void onSuccess(Coin fee) {
|
||||
if (!resultFuture.isDone()) {
|
||||
|
@ -52,5 +40,5 @@ public class BlockchainService {
|
|||
});
|
||||
}
|
||||
return resultFuture;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -13,13 +13,13 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
class GetFeeRequest {
|
||||
private static final Logger log = LoggerFactory.getLogger(GetFeeRequest.class);
|
||||
class GetTransactionRequest {
|
||||
private static final Logger log = LoggerFactory.getLogger(GetTransactionRequest.class);
|
||||
private static final ListeningExecutorService executorService = Utilities.getListeningExecutorService("GetFeeRequest", 3, 5, 10 * 60);
|
||||
private Timer timer;
|
||||
private int faults;
|
||||
|
||||
public GetFeeRequest() {
|
||||
public GetTransactionRequest() {
|
||||
}
|
||||
|
||||
public SettableFuture<Coin> request(String transactionId, BlockchainTxProvider provider) {
|
|
@ -1,6 +1,6 @@
|
|||
package io.bitsquare.btc.blockchain.providers;
|
||||
|
||||
import io.bitsquare.btc.HttpClientProvider;
|
||||
import io.bitsquare.btc.provider.HttpClientProvider;
|
||||
import io.bitsquare.http.HttpClient;
|
||||
import io.bitsquare.http.HttpException;
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
package io.bitsquare.btc.pricefeed;
|
||||
|
||||
class PriceRequestException extends Exception {
|
||||
public PriceRequestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.bitsquare.btc;
|
||||
package io.bitsquare.btc.provider;
|
||||
|
||||
import io.bitsquare.http.HttpClient;
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package io.bitsquare.btc.provider;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.bitsquare.app.AppOptionKeys;
|
||||
import io.bitsquare.network.NetworkOptionKeys;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import java.util.Random;
|
||||
|
||||
public class ProvidersRepository {
|
||||
private static final Logger log = LoggerFactory.getLogger(ProvidersRepository.class);
|
||||
|
||||
private final String[] providerArray;
|
||||
private String baseUrl;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public ProvidersRepository(@Named(AppOptionKeys.PROVIDERS) String providers,
|
||||
@Named(NetworkOptionKeys.USE_LOCALHOST) boolean useLocalhost) {
|
||||
if (providers.isEmpty()) {
|
||||
if (useLocalhost) {
|
||||
// If we run in localhost mode we don't have the tor node running, so we need a clearnet host
|
||||
// providers = "http://95.85.11.205:8080/";
|
||||
|
||||
// Use localhost for using a locally running priceprovider
|
||||
providers = "http://localhost:8080/, http://95.85.11.205:8080/";
|
||||
//providers = "http://localhost:8080/";
|
||||
} else {
|
||||
providers = "http://t4wlzy7l6k4hnolg.onion/, http://g27szt7aw2vrtowe.onion/";
|
||||
}
|
||||
}
|
||||
providerArray = providers.replace(" ", "").split(",");
|
||||
int index = new Random().nextInt(providerArray.length);
|
||||
baseUrl = providerArray[index];
|
||||
log.info("baseUrl for PriceFeedService: " + baseUrl);
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public boolean hasMoreProviders() {
|
||||
return providerArray.length > 1;
|
||||
}
|
||||
|
||||
public void setNewRandomBaseUrl() {
|
||||
String newBaseUrl;
|
||||
do {
|
||||
int index = new Random().nextInt(providerArray.length);
|
||||
newBaseUrl = providerArray[index];
|
||||
}
|
||||
while (baseUrl.equals(newBaseUrl));
|
||||
baseUrl = newBaseUrl;
|
||||
log.info("Try new baseUrl after error: " + baseUrl);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package io.bitsquare.btc.provider.fee;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FeeData {
|
||||
private static final Logger log = LoggerFactory.getLogger(FeeData.class);
|
||||
|
||||
public final long txFee;
|
||||
public final long createOfferFee;
|
||||
public final long takeOfferFee;
|
||||
|
||||
public FeeData(long txFee, long createOfferFee, long takeOfferFee) {
|
||||
this.txFee = txFee;
|
||||
this.createOfferFee = createOfferFee;
|
||||
this.takeOfferFee = takeOfferFee;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package io.bitsquare.btc.provider.fee;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
import io.bitsquare.app.Version;
|
||||
import io.bitsquare.btc.provider.HttpClientProvider;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
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;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FeeProvider extends HttpClientProvider {
|
||||
private static final Logger log = LoggerFactory.getLogger(FeeProvider.class);
|
||||
private final String uid;
|
||||
|
||||
public FeeProvider(HttpClient httpClient, String baseUrl) {
|
||||
super(httpClient, baseUrl, false);
|
||||
|
||||
uid = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public Tuple2<Map<String, Long>, FeeData> getFees() throws IOException, HttpException {
|
||||
String json = httpClient.requestWithGET("getFees", "User-Agent", "Bitsquare/" + Version.VERSION + ", uid:" + uid);
|
||||
LinkedTreeMap<String, Object> linkedTreeMap = new Gson().fromJson(json, LinkedTreeMap.class);
|
||||
Map<String, Long> tsMap = new HashMap<>();
|
||||
tsMap.put("bitcoinFeesTs", ((Double) linkedTreeMap.get("bitcoinFeesTs")).longValue());
|
||||
|
||||
LinkedTreeMap<String, Double> dataMap = (LinkedTreeMap<String, Double>) linkedTreeMap.get("data");
|
||||
FeeData feeData = new FeeData(dataMap.get("txFee").longValue(), dataMap.get("createOfferFee").longValue(), dataMap.get("takeOfferFee").longValue());
|
||||
return new Tuple2<>(tsMap, feeData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FeeProvider";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package io.bitsquare.btc.provider.fee;
|
||||
|
||||
import com.google.common.util.concurrent.*;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import io.bitsquare.common.util.Utilities;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class FeeRequest {
|
||||
private static final Logger log = LoggerFactory.getLogger(FeeRequest.class);
|
||||
|
||||
private static final ListeningExecutorService executorService = Utilities.getListeningExecutorService("FeeRequest", 3, 5, 10 * 60);
|
||||
|
||||
public FeeRequest() {
|
||||
}
|
||||
|
||||
public SettableFuture<Tuple2<Map<String, Long>, FeeData>> getFees(FeeProvider provider) {
|
||||
final SettableFuture<Tuple2<Map<String, Long>, FeeData>> resultFuture = SettableFuture.create();
|
||||
ListenableFuture<Tuple2<Map<String, Long>, FeeData>> future = executorService.submit(() -> {
|
||||
Thread.currentThread().setName("FeeRequest-" + provider.toString());
|
||||
return provider.getFees();
|
||||
});
|
||||
|
||||
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Long>, FeeData>>() {
|
||||
public void onSuccess(Tuple2<Map<String, Long>, FeeData> feeData) {
|
||||
log.debug("Received feeData of {}\nfrom provider {}", feeData, provider);
|
||||
resultFuture.set(feeData);
|
||||
}
|
||||
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
resultFuture.setException(throwable);
|
||||
}
|
||||
});
|
||||
|
||||
return resultFuture;
|
||||
}
|
||||
}
|
126
core/src/main/java/io/bitsquare/btc/provider/fee/FeeService.java
Normal file
126
core/src/main/java/io/bitsquare/btc/provider/fee/FeeService.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.btc.provider.fee;
|
||||
|
||||
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.provider.ProvidersRepository;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.handlers.FaultHandler;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import io.bitsquare.http.HttpClient;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class FeeService {
|
||||
private static final Logger log = LoggerFactory.getLogger(FeeService.class);
|
||||
|
||||
public static final long MIN_TX_FEE = 40; // satoshi/byte
|
||||
public static final long MAX_TX_FEE = 200;
|
||||
public static final long DEFAULT_TX_FEE = 60;
|
||||
|
||||
public static final long MIN_CREATE_OFFER_FEE = 50_000;
|
||||
public static final long MAX_CREATE_OFFER_FEE = 500_000;
|
||||
public static final long DEFAULT_CREATE_OFFER_FEE = 50_000;
|
||||
|
||||
public static final long MIN_TAKE_OFFER_FEE = 100_000;
|
||||
public static final long MAX_TAKE_OFFER_FEE = 1000_000;
|
||||
public static final long DEFAULT_TAKE_OFFER_FEE = 100_000;
|
||||
|
||||
private final FeeProvider feeProvider;
|
||||
private final ProvidersRepository providersRepository;
|
||||
private final HttpClient httpClient;
|
||||
private FeeData feeData;
|
||||
private Map<String, Long> timeStampMap;
|
||||
private long epochInSecondAtLastRequest;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public FeeService(HttpClient httpClient,
|
||||
ProvidersRepository providersRepository) {
|
||||
this.httpClient = httpClient;
|
||||
this.providersRepository = providersRepository;
|
||||
this.feeProvider = new FeeProvider(httpClient, providersRepository.getBaseUrl());
|
||||
feeData = new FeeData(DEFAULT_TX_FEE, DEFAULT_CREATE_OFFER_FEE, DEFAULT_TAKE_OFFER_FEE);
|
||||
}
|
||||
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
requestFees(null, null);
|
||||
}
|
||||
|
||||
public void requestFees(@Nullable Runnable resultHandler, @Nullable FaultHandler faultHandler) {
|
||||
//TODO add throttle
|
||||
Log.traceCall();
|
||||
FeeRequest feeRequest = new FeeRequest();
|
||||
SettableFuture<Tuple2<Map<String, Long>, FeeData>> future = feeRequest.getFees(feeProvider);
|
||||
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Long>, FeeData>>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Tuple2<Map<String, Long>, FeeData> result) {
|
||||
UserThread.execute(() -> {
|
||||
checkNotNull(result, "Result must not be null at getFees");
|
||||
timeStampMap = result.first;
|
||||
epochInSecondAtLastRequest = timeStampMap.get("bitcoinFeesTs");
|
||||
feeData = result.second;
|
||||
if (resultHandler != null)
|
||||
resultHandler.run();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
log.warn("Could not load fees. " + throwable.toString());
|
||||
if (faultHandler != null)
|
||||
UserThread.execute(() -> faultHandler.handleFault("Could not load fees", throwable));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Coin getTxFee() {
|
||||
// feeData.txFee is sat/byte but we want satoshi / kb
|
||||
log.debug("getTxFee " + (feeData.txFee * 1000));
|
||||
return Coin.valueOf(feeData.txFee * 1000);
|
||||
}
|
||||
|
||||
// TODO needed?
|
||||
public Coin getTxFeeForWithdrawal() {
|
||||
return getTxFee();
|
||||
}
|
||||
|
||||
public Coin getCreateOfferFee() {
|
||||
return Coin.valueOf(feeData.createOfferFee);
|
||||
}
|
||||
|
||||
public Coin getTakeOfferFee() {
|
||||
return Coin.valueOf(feeData.takeOfferFee);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.bitsquare.btc.pricefeed;
|
||||
package io.bitsquare.btc.provider.price;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
|
@ -1,22 +1,20 @@
|
|||
package io.bitsquare.btc.pricefeed;
|
||||
package io.bitsquare.btc.provider.price;
|
||||
|
||||
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.AppOptionKeys;
|
||||
import io.bitsquare.app.Log;
|
||||
import io.bitsquare.btc.provider.ProvidersRepository;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.handlers.FaultHandler;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import io.bitsquare.http.HttpClient;
|
||||
import io.bitsquare.network.NetworkOptionKeys;
|
||||
import javafx.beans.property.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Named;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
@ -28,7 +26,9 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
|
||||
public class PriceFeedService {
|
||||
private static final Logger log = LoggerFactory.getLogger(PriceFeedService.class);
|
||||
private HttpClient httpClient;
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final ProvidersRepository providersRepository;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -60,8 +60,6 @@ public class PriceFeedService {
|
|||
private final IntegerProperty currenciesUpdateFlag = new SimpleIntegerProperty(0);
|
||||
private long epochInSecondAtLastRequest;
|
||||
private Map<String, Long> timeStampMap = new HashMap<>();
|
||||
private String baseUrl;
|
||||
private final String[] priceFeedProviderArray;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -69,26 +67,10 @@ public class PriceFeedService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public PriceFeedService(HttpClient httpClient,
|
||||
@Named(AppOptionKeys.PRICE_FEED_PROVIDERS) String priceFeedProviders,
|
||||
@Named(NetworkOptionKeys.USE_LOCALHOST) boolean useLocalhost) {
|
||||
public PriceFeedService(HttpClient httpClient, ProvidersRepository providersRepository) {
|
||||
this.httpClient = httpClient;
|
||||
if (priceFeedProviders.isEmpty()) {
|
||||
if (useLocalhost) {
|
||||
// If we run in localhost mode we don't have the tor node running, so we need a clearnet host
|
||||
priceFeedProviders = "http://95.85.11.205:8080/";
|
||||
|
||||
// Use localhost for using a locally running priceprovider
|
||||
// priceFeedProviders = "http://localhost:8080/";
|
||||
} else {
|
||||
priceFeedProviders = "http://t4wlzy7l6k4hnolg.onion/, http://g27szt7aw2vrtowe.onion/";
|
||||
}
|
||||
}
|
||||
priceFeedProviderArray = priceFeedProviders.replace(" ", "").split(",");
|
||||
int index = new Random().nextInt(priceFeedProviderArray.length);
|
||||
baseUrl = priceFeedProviderArray[index];
|
||||
log.info("baseUrl for PriceFeedService: " + baseUrl);
|
||||
this.priceProvider = new PriceProvider(httpClient, baseUrl);
|
||||
this.providersRepository = providersRepository;
|
||||
this.priceProvider = new PriceProvider(httpClient, providersRepository.getBaseUrl());
|
||||
}
|
||||
|
||||
|
||||
|
@ -112,19 +94,9 @@ public class PriceFeedService {
|
|||
}, (errorMessage, throwable) -> {
|
||||
|
||||
// Try other provider if more then 1 is available
|
||||
if (priceFeedProviderArray.length > 1) {
|
||||
String newBaseUrl;
|
||||
do {
|
||||
int index = new Random().nextInt(priceFeedProviderArray.length);
|
||||
log.error(index + "");
|
||||
newBaseUrl = priceFeedProviderArray[index];
|
||||
log.error(newBaseUrl);
|
||||
}
|
||||
while (baseUrl.equals(newBaseUrl));
|
||||
baseUrl = newBaseUrl;
|
||||
|
||||
log.info("Try new baseUrl after error: " + baseUrl);
|
||||
this.priceProvider = new PriceProvider(httpClient, baseUrl);
|
||||
if (providersRepository.hasMoreProviders()) {
|
||||
providersRepository.setNewRandomBaseUrl();
|
||||
priceProvider = new PriceProvider(httpClient, providersRepository.getBaseUrl());
|
||||
request();
|
||||
} else {
|
||||
UserThread.runAfter(this::request, 120);
|
|
@ -1,9 +1,9 @@
|
|||
package io.bitsquare.btc.pricefeed;
|
||||
package io.bitsquare.btc.provider.price;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
import io.bitsquare.app.Version;
|
||||
import io.bitsquare.btc.HttpClientProvider;
|
||||
import io.bitsquare.btc.provider.HttpClientProvider;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import io.bitsquare.http.HttpClient;
|
||||
import io.bitsquare.http.HttpException;
|
||||
|
@ -25,7 +25,7 @@ public class PriceProvider extends HttpClientProvider {
|
|||
|
||||
public Tuple2<Map<String, Long>, Map<String, MarketPrice>> getAll() throws IOException, HttpException {
|
||||
Map<String, MarketPrice> marketPriceMap = new HashMap<>();
|
||||
String json = httpClient.requestWithGET("all", "User-Agent", "Bitsquare/" + Version.VERSION + ", uid:" + uid);
|
||||
String json = httpClient.requestWithGET("getAllMarketPrices", "User-Agent", "Bitsquare/" + Version.VERSION + ", uid:" + uid);
|
||||
LinkedTreeMap<String, Object> map = new Gson().fromJson(json, LinkedTreeMap.class);
|
||||
Map<String, Long> tsMap = new HashMap<>();
|
||||
tsMap.put("btcAverageTs", ((Double) map.get("btcAverageTs")).longValue());
|
|
@ -1,4 +1,4 @@
|
|||
package io.bitsquare.btc.pricefeed;
|
||||
package io.bitsquare.btc.provider.price;
|
||||
|
||||
import com.google.common.util.concurrent.*;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
|
@ -9,7 +9,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
class PriceRequest {
|
||||
public class PriceRequest {
|
||||
private static final Logger log = LoggerFactory.getLogger(PriceRequest.class);
|
||||
|
||||
private static final ListeningExecutorService executorService = Utilities.getListeningExecutorService("PriceRequest", 3, 5, 10 * 60);
|
|
@ -0,0 +1,7 @@
|
|||
package io.bitsquare.btc.provider.price;
|
||||
|
||||
public class PriceRequestException extends Exception {
|
||||
public PriceRequestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -132,6 +132,7 @@ public class CurrencyUtil {
|
|||
result.add(new CryptoCurrency("HODL", "HOdlcoin"));
|
||||
result.add(new CryptoCurrency("HNC", "HunCoin"));
|
||||
result.add(new CryptoCurrency("IOC", "I/O Coin"));
|
||||
result.add(new CryptoCurrency("IOP", "Fermat"));
|
||||
result.add(new CryptoCurrency("JPYT", "JPY Tether"));
|
||||
result.add(new CryptoCurrency("JBS", "Jumbucks"));
|
||||
result.add(new CryptoCurrency("LBC", "LBRY Credits"));
|
||||
|
|
|
@ -24,7 +24,7 @@ import io.bitsquare.btc.AddressEntry;
|
|||
import io.bitsquare.btc.AddressEntryException;
|
||||
import io.bitsquare.btc.TradeWalletService;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.crypto.KeyRing;
|
||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package io.bitsquare.trade.closed;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.crypto.KeyRing;
|
||||
import io.bitsquare.storage.Storage;
|
||||
import io.bitsquare.trade.Tradable;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package io.bitsquare.trade.failed;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.crypto.KeyRing;
|
||||
import io.bitsquare.storage.Storage;
|
||||
import io.bitsquare.trade.TradableList;
|
||||
|
|
|
@ -20,8 +20,8 @@ package io.bitsquare.trade.offer;
|
|||
import io.bitsquare.app.DevFlags;
|
||||
import io.bitsquare.app.Version;
|
||||
import io.bitsquare.btc.Restrictions;
|
||||
import io.bitsquare.btc.pricefeed.MarketPrice;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.MarketPrice;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.crypto.KeyRing;
|
||||
import io.bitsquare.common.crypto.PubKeyRing;
|
||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||
|
|
|
@ -19,7 +19,7 @@ package io.bitsquare.trade.offer;
|
|||
|
||||
import com.google.inject.name.Named;
|
||||
import io.bitsquare.app.AppOptionKeys;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||
import io.bitsquare.common.handlers.ResultHandler;
|
||||
|
|
|
@ -23,7 +23,7 @@ import io.bitsquare.app.Log;
|
|||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.TradeWalletService;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.Timer;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.crypto.KeyRing;
|
||||
|
|
|
@ -19,7 +19,6 @@ package io.bitsquare.trade.protocol.placeoffer.tasks;
|
|||
|
||||
import io.bitsquare.arbitration.Arbitrator;
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.common.taskrunner.Task;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
|
@ -56,7 +55,7 @@ public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
|
|||
walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(),
|
||||
model.reservedFundsForOffer,
|
||||
model.useSavingsWallet,
|
||||
FeePolicy.getCreateOfferFee(),
|
||||
model.offer.getCreateOfferFee(),
|
||||
model.offer.getTxFee(),
|
||||
selectedArbitrator.getBtcAddress());
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ public class RestrictionsTest {
|
|||
@Test
|
||||
public void testIsMinSpendableAmount() {
|
||||
Coin amount = null;
|
||||
Coin txFee = new FeeService().getTxFee();
|
||||
Coin txFee = Coin.valueOf(20000);
|
||||
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeForTradesAndDust(amount, txFee));
|
||||
|
||||
amount = Coin.ZERO;
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
package io.bitsquare.btc.blockchain;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
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
|
||||
/* @Test
|
||||
public void testGetFee() throws InterruptedException {
|
||||
BlockchainService blockchainService = new BlockchainService(null, null, null);
|
||||
|
||||
|
@ -35,5 +27,5 @@ public class BlockchainServiceTest {
|
|||
}
|
||||
});
|
||||
Thread.sleep(5000);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.bitsquare.btc.pricefeed;
|
||||
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -13,7 +14,7 @@ public class MarketPriceFeedServiceTest {
|
|||
|
||||
@Test
|
||||
public void testGetPrice() throws InterruptedException {
|
||||
PriceFeedService priceFeedService = new PriceFeedService(null, null, true);
|
||||
PriceFeedService priceFeedService = new PriceFeedService(null, null);
|
||||
priceFeedService.setCurrencyCode("EUR");
|
||||
priceFeedService.init(tradeCurrency -> {
|
||||
log.debug(tradeCurrency.toString());
|
||||
|
|
|
@ -20,7 +20,7 @@ package io.bitsquare.gui.main;
|
|||
import io.bitsquare.BitsquareException;
|
||||
import io.bitsquare.app.BitsquareApp;
|
||||
import io.bitsquare.app.DevFlags;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import io.bitsquare.common.util.Tuple3;
|
||||
|
|
|
@ -33,8 +33,9 @@ import io.bitsquare.btc.AddressEntry;
|
|||
import io.bitsquare.btc.TradeWalletService;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.btc.pricefeed.MarketPrice;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.btc.provider.price.MarketPrice;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.Clock;
|
||||
import io.bitsquare.common.Timer;
|
||||
import io.bitsquare.common.UserThread;
|
||||
|
@ -106,14 +107,15 @@ public class MainViewModel implements ViewModel {
|
|||
private final DisputeManager disputeManager;
|
||||
final Preferences preferences;
|
||||
private final AlertManager alertManager;
|
||||
private PrivateNotificationManager privateNotificationManager;
|
||||
private FilterManager filterManager;
|
||||
private final PrivateNotificationManager privateNotificationManager;
|
||||
private final FilterManager filterManager;
|
||||
private final WalletPasswordWindow walletPasswordWindow;
|
||||
private AddBitcoinNodesWindow addBitcoinNodesWindow;
|
||||
private final AddBitcoinNodesWindow addBitcoinNodesWindow;
|
||||
private final NotificationCenter notificationCenter;
|
||||
private final TacWindow tacWindow;
|
||||
private Clock clock;
|
||||
private KeyRing keyRing;
|
||||
private final Clock clock;
|
||||
private final FeeService feeService;
|
||||
private final KeyRing keyRing;
|
||||
private final Navigation navigation;
|
||||
private final BSFormatter formatter;
|
||||
|
||||
|
@ -180,7 +182,7 @@ public class MainViewModel implements ViewModel {
|
|||
OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences,
|
||||
User user, AlertManager alertManager, PrivateNotificationManager privateNotificationManager,
|
||||
FilterManager filterManager, WalletPasswordWindow walletPasswordWindow, AddBitcoinNodesWindow addBitcoinNodesWindow,
|
||||
NotificationCenter notificationCenter, TacWindow tacWindow, Clock clock,
|
||||
NotificationCenter notificationCenter, TacWindow tacWindow, Clock clock, FeeService feeService,
|
||||
KeyRing keyRing, Navigation navigation, BSFormatter formatter) {
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.user = user;
|
||||
|
@ -200,6 +202,7 @@ public class MainViewModel implements ViewModel {
|
|||
this.notificationCenter = notificationCenter;
|
||||
this.tacWindow = tacWindow;
|
||||
this.clock = clock;
|
||||
this.feeService = feeService;
|
||||
this.keyRing = keyRing;
|
||||
this.navigation = navigation;
|
||||
this.formatter = formatter;
|
||||
|
@ -560,6 +563,8 @@ public class MainViewModel implements ViewModel {
|
|||
|
||||
p2PService.onAllServicesInitialized();
|
||||
|
||||
feeService.onAllServicesInitialized();
|
||||
|
||||
setupBtcNumPeersWatcher();
|
||||
setupP2PNumPeersWatcher();
|
||||
updateBalance();
|
||||
|
|
|
@ -20,10 +20,10 @@ package io.bitsquare.gui.main.funds.deposit;
|
|||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import io.bitsquare.app.DevFlags;
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.FeeService;
|
||||
import io.bitsquare.btc.Restrictions;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
|
|
|
@ -20,7 +20,6 @@ package io.bitsquare.gui.main.funds.transactions;
|
|||
import com.googlecode.jcsv.writer.CSVEntryConverter;
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import io.bitsquare.arbitration.DisputeManager;
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import io.bitsquare.common.util.Tuple4;
|
||||
|
@ -596,13 +595,15 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
|
||||
// TODO fee is dynamic now
|
||||
Coin txFee = Coin.valueOf(20_000);
|
||||
Coin createOfferFee = Coin.valueOf(50_000);
|
||||
Coin takeOfferFee = Coin.valueOf(100_000);
|
||||
|
||||
if (!dataByDayMap.containsKey(day)) {
|
||||
int numOffers = 0;
|
||||
int numTrades = 0;
|
||||
if (amountAsCoin.compareTo(FeePolicy.getCreateOfferFee().subtract(txFee)) == 0)
|
||||
if (amountAsCoin.compareTo(createOfferFee.subtract(txFee)) == 0)
|
||||
numOffers++;
|
||||
else if (amountAsCoin.compareTo(FeePolicy.getTakeOfferFee().subtract(txFee)) == 0)
|
||||
else if (amountAsCoin.compareTo(takeOfferFee.subtract(txFee)) == 0)
|
||||
numTrades++;
|
||||
|
||||
dataByDayMap.put(day, new Tuple4<>(item.getDate(), 1, numOffers, numTrades));
|
||||
|
@ -611,9 +612,9 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
int prev = tuple.second;
|
||||
int numOffers = tuple.third;
|
||||
int numTrades = tuple.forth;
|
||||
if (amountAsCoin.compareTo(FeePolicy.getCreateOfferFee().subtract(txFee)) == 0)
|
||||
if (amountAsCoin.compareTo(createOfferFee.subtract(txFee)) == 0)
|
||||
numOffers++;
|
||||
else if (amountAsCoin.compareTo(FeePolicy.getTakeOfferFee().subtract(txFee)) == 0)
|
||||
else if (amountAsCoin.compareTo(takeOfferFee.subtract(txFee)) == 0)
|
||||
numTrades++;
|
||||
|
||||
dataByDayMap.put(day, new Tuple4<>(tuple.first, ++prev, numOffers, numTrades));
|
||||
|
|
|
@ -19,7 +19,7 @@ package io.bitsquare.gui.main.market.offerbook;
|
|||
|
||||
import com.google.common.math.LongMath;
|
||||
import com.google.inject.Inject;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.model.ActivatableViewModel;
|
||||
import io.bitsquare.gui.main.MainView;
|
||||
|
|
|
@ -19,7 +19,7 @@ package io.bitsquare.gui.main.market.trades;
|
|||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.inject.Inject;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.util.MathUtils;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.model.ActivatableViewModel;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package io.bitsquare.gui.main.offer;
|
||||
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import io.bitsquare.gui.common.view.ViewLoader;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package io.bitsquare.gui.main.offer;
|
||||
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package io.bitsquare.gui.main.offer;
|
||||
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import io.bitsquare.gui.common.view.ViewLoader;
|
||||
|
|
|
@ -21,16 +21,21 @@ import com.google.inject.Inject;
|
|||
import io.bitsquare.app.DevFlags;
|
||||
import io.bitsquare.app.Version;
|
||||
import io.bitsquare.arbitration.Arbitrator;
|
||||
import io.bitsquare.btc.*;
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.btc.TradeWalletService;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.blockchain.BlockchainService;
|
||||
import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.crypto.KeyRing;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.model.ActivatableDataModel;
|
||||
import io.bitsquare.gui.main.offer.createoffer.monetary.Price;
|
||||
import io.bitsquare.gui.main.offer.createoffer.monetary.Volume;
|
||||
import io.bitsquare.gui.main.overlays.notifications.Notification;
|
||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import io.bitsquare.locale.CurrencyUtil;
|
||||
import io.bitsquare.locale.TradeCurrency;
|
||||
|
@ -68,14 +73,15 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
|||
private final P2PService p2PService;
|
||||
private final PriceFeedService priceFeedService;
|
||||
final String shortOfferId;
|
||||
private Navigation navigation;
|
||||
private final FeeService feeService;
|
||||
private final Navigation navigation;
|
||||
private final BlockchainService blockchainService;
|
||||
private final BSFormatter formatter;
|
||||
private final String offerId;
|
||||
private final AddressEntry addressEntry;
|
||||
private final Coin createOfferFeeAsCoin, takeOfferAsCoin;
|
||||
private final Coin txFeeAsCoin;
|
||||
private final Coin securityDepositAsCoin;
|
||||
private Coin createOfferFeeAsCoin, takerFeeAsCoin;
|
||||
private Coin txFeeAsCoin;
|
||||
private Coin securityDepositAsCoin;
|
||||
private final BalanceListener balanceListener;
|
||||
private final SetChangeListener<PaymentAccount> paymentAccountsChangeListener;
|
||||
|
||||
|
@ -128,6 +134,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
|||
this.keyRing = keyRing;
|
||||
this.p2PService = p2PService;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.feeService = feeService;
|
||||
this.navigation = navigation;
|
||||
this.blockchainService = blockchainService;
|
||||
this.formatter = formatter;
|
||||
|
@ -137,11 +144,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
|||
offerId = UUID.randomUUID().toString();
|
||||
shortOfferId = offerId.substring(0, Math.min(8, offerId.length()));
|
||||
addressEntry = walletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING);
|
||||
createOfferFeeAsCoin = feeService.getCreateOfferFee();
|
||||
takeOfferAsCoin = feeService.getTakeOfferFee();
|
||||
txFeeAsCoin = feeService.getTxFee();
|
||||
// TODO
|
||||
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
|
||||
|
||||
useMarketBasedPrice.set(preferences.getUsePercentageBasedPrice());
|
||||
|
||||
|
@ -182,6 +184,21 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
|||
addBindings();
|
||||
addListeners();
|
||||
|
||||
feeService.requestFees(() -> {
|
||||
//TODO update doubleTxFeeAsCoin and txFeeAsCoin in view with binding
|
||||
createOfferFeeAsCoin = feeService.getCreateOfferFee();
|
||||
takerFeeAsCoin = feeService.getTakeOfferFee();
|
||||
txFeeAsCoin = feeService.getTxFee();
|
||||
calculateTotalToPay();
|
||||
}, (errorMessage, throwable) -> new Popup<>().warning(errorMessage).show());
|
||||
|
||||
createOfferFeeAsCoin = feeService.getCreateOfferFee();
|
||||
takerFeeAsCoin = feeService.getTakeOfferFee();
|
||||
txFeeAsCoin = feeService.getTxFee();
|
||||
|
||||
// TODO
|
||||
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
|
||||
|
||||
if (!preferences.getUseStickyMarketPrice() && isTabSelected)
|
||||
priceFeedService.setCurrencyCode(tradeCurrencyCode.get());
|
||||
|
||||
|
@ -322,7 +339,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
|||
Version.VERSION,
|
||||
txFeeAsCoin.value,
|
||||
createOfferFeeAsCoin.value,
|
||||
takeOfferAsCoin.value,
|
||||
takerFeeAsCoin.value,
|
||||
securityDepositAsCoin.value,
|
||||
maxTradeLimit,
|
||||
maxTradePeriod,
|
||||
|
@ -332,7 +349,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
|||
}
|
||||
|
||||
void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) {
|
||||
openOfferManager.placeOffer(offer, totalToPayAsCoin.get().subtract(createOfferFeeAsCoin), useSavingsWallet, resultHandler);
|
||||
openOfferManager.placeOffer(offer, totalToPayAsCoin.get().subtract(txFeeAsCoin).subtract(createOfferFeeAsCoin), useSavingsWallet, resultHandler);
|
||||
}
|
||||
|
||||
public void onPaymentAccountSelected(PaymentAccount paymentAccount) {
|
||||
|
@ -486,9 +503,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
|||
// created the offer and reserved his funds, so that would not work well with dynamic fees.
|
||||
// The mining fee for the createOfferFee tx is deducted from the createOfferFee and not visible to the trader
|
||||
if (direction != null && amount.get() != null) {
|
||||
Coin feeAndSecDeposit = createOfferFeeAsCoin.add(securityDepositAsCoin);
|
||||
Coin feeAndSecDepositAndAmount = feeAndSecDeposit.add(amount.get());
|
||||
Coin required = direction == Offer.Direction.BUY ? feeAndSecDeposit : feeAndSecDepositAndAmount;
|
||||
Coin feeAndSecDeposit = createOfferFeeAsCoin.add(txFeeAsCoin).add(securityDepositAsCoin);
|
||||
Coin required = direction == Offer.Direction.BUY ? feeAndSecDeposit : feeAndSecDeposit.add(amount.get());
|
||||
totalToPayAsCoin.set(required);
|
||||
log.debug("totalToPayAsCoin " + totalToPayAsCoin.get().toFriendlyString());
|
||||
updateBalance();
|
||||
|
@ -538,6 +554,10 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
|||
return createOfferFeeAsCoin;
|
||||
}
|
||||
|
||||
public Coin getTxFeeAsCoin() {
|
||||
return txFeeAsCoin;
|
||||
}
|
||||
|
||||
public Coin getSecurityDepositAsCoin() {
|
||||
return securityDepositAsCoin;
|
||||
}
|
||||
|
|
|
@ -345,7 +345,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
|||
"The amount is the sum of:\n" +
|
||||
tradeAmountText +
|
||||
"- Security deposit: " + model.getSecurityDeposit() + "\n" +
|
||||
"- Trading fee: " + model.getOfferFee() + "\n\n" +
|
||||
"- Trading fee: " + model.getOfferFee() + "\n" +
|
||||
"- Mining fee: " + model.getTxFee() + "\n\n" +
|
||||
|
||||
"You can choose between two options when funding your trade:\n" +
|
||||
"- Use your Bitsquare wallet (convenient, but transactions may be linkable) OR\n" +
|
||||
|
@ -1078,6 +1079,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
|||
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.securityDeposit"), model.getSecurityDeposit());
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.offerFee"), model.getOfferFee());
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.networkFee"), model.getTxFee());
|
||||
Separator separator = new Separator();
|
||||
separator.setOrientation(Orientation.HORIZONTAL);
|
||||
separator.setStyle("-fx-background: #666;");
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
package io.bitsquare.gui.main.offer.createoffer;
|
||||
|
||||
import io.bitsquare.app.DevFlags;
|
||||
import io.bitsquare.btc.pricefeed.MarketPrice;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.MarketPrice;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.Timer;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.util.MathUtils;
|
||||
|
@ -663,6 +663,10 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
return formatter.formatCoinWithCode(dataModel.getCreateOfferFeeAsCoin());
|
||||
}
|
||||
|
||||
public String getTxFee() {
|
||||
return formatter.formatCoinWithCode(dataModel.getTxFeeAsCoin());
|
||||
}
|
||||
|
||||
public String getSecurityDeposit() {
|
||||
return formatter.formatCoinWithCode(dataModel.getSecurityDepositAsCoin());
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package io.bitsquare.gui.main.offer.offerbook;
|
||||
|
||||
import io.bitsquare.alert.PrivateNotificationManager;
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
|
@ -395,7 +394,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
String key = "RemoveOfferWarning";
|
||||
if (model.preferences.showAgain(key))
|
||||
new Popup().warning("Are you sure you want to remove that offer?\n" +
|
||||
"The offer fee of " + model.formatter.formatCoinWithCode(FeePolicy.getCreateOfferFee()) +
|
||||
"The offer fee of " + model.formatter.formatCoinWithCode(offer.getCreateOfferFee()) +
|
||||
" will be lost if you remove that offer.")
|
||||
.actionButtonText("Remove offer")
|
||||
.onAction(() -> doRemoveOffer(offer))
|
||||
|
|
|
@ -20,7 +20,7 @@ package io.bitsquare.gui.main.offer.offerbook;
|
|||
import com.google.common.base.Joiner;
|
||||
import com.google.inject.Inject;
|
||||
import io.bitsquare.app.Version;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||
import io.bitsquare.common.handlers.ResultHandler;
|
||||
import io.bitsquare.filter.FilterManager;
|
||||
|
|
|
@ -20,10 +20,14 @@ package io.bitsquare.gui.main.offer.takeoffer;
|
|||
import com.google.inject.Inject;
|
||||
import io.bitsquare.app.DevFlags;
|
||||
import io.bitsquare.arbitration.Arbitrator;
|
||||
import io.bitsquare.btc.*;
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.btc.TradeWalletService;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.blockchain.BlockchainService;
|
||||
import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.gui.common.model.ActivatableDataModel;
|
||||
import io.bitsquare.gui.main.overlays.notifications.Notification;
|
||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||
|
@ -59,15 +63,16 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
final TradeWalletService tradeWalletService;
|
||||
final WalletService walletService;
|
||||
private final User user;
|
||||
private final FeeService feeService;
|
||||
private final Preferences preferences;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final BlockchainService blockchainService;
|
||||
private final BSFormatter formatter;
|
||||
|
||||
private Coin takerFeeAsCoin;
|
||||
private final Coin txFeeAsCoin;
|
||||
private final Coin doubleTxFeeAsCoin;
|
||||
private final Coin securityDepositAsCoin;
|
||||
private Coin txFeeAsCoin;
|
||||
private Coin totalTxFeeAsCoin;
|
||||
private Coin securityDepositAsCoin;
|
||||
// Coin feeFromFundingTx = Coin.NEGATIVE_SATOSHI;
|
||||
|
||||
private Offer offer;
|
||||
|
@ -106,19 +111,12 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
this.tradeWalletService = tradeWalletService;
|
||||
this.walletService = walletService;
|
||||
this.user = user;
|
||||
this.feeService = feeService;
|
||||
this.preferences = preferences;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.blockchainService = blockchainService;
|
||||
this.formatter = formatter;
|
||||
|
||||
// Taker pays 2 times the tx fee because the mining fee might be different when offerer created the offer
|
||||
// and reserved his funds, so that would not work well with dynamic fees.
|
||||
// The mining fee for the takeOfferFee tx is deducted from the createOfferFee and not visible to the trader
|
||||
txFeeAsCoin = feeService.getTxFee();
|
||||
doubleTxFeeAsCoin = txFeeAsCoin.add(txFeeAsCoin);
|
||||
//TODO
|
||||
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
|
||||
|
||||
// isMainNet.set(preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET);
|
||||
}
|
||||
|
||||
|
@ -177,6 +175,24 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
if (DevFlags.DEV_MODE)
|
||||
amountAsCoin.set(offer.getAmount());
|
||||
|
||||
// Taker pays 2 times the tx fee because the mining fee might be different when offerer created the offer
|
||||
// and reserved his funds, so that would not work well with dynamic fees.
|
||||
// The mining fee for the takeOfferFee tx is deducted from the createOfferFee and not visible to the trader
|
||||
feeService.requestFees(() -> {
|
||||
//TODO update doubleTxFeeAsCoin and txFeeAsCoin in view with binding
|
||||
takerFeeAsCoin = feeService.getTakeOfferFee();
|
||||
txFeeAsCoin = feeService.getTxFee();
|
||||
totalTxFeeAsCoin = txFeeAsCoin.multiply(3);
|
||||
calculateTotalToPay();
|
||||
}, (errorMessage, throwable) -> new Popup<>().warning(errorMessage).show());
|
||||
|
||||
takerFeeAsCoin = feeService.getTakeOfferFee();
|
||||
txFeeAsCoin = feeService.getTxFee();
|
||||
totalTxFeeAsCoin = txFeeAsCoin.multiply(3);
|
||||
|
||||
//TODO
|
||||
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
|
||||
|
||||
calculateVolume();
|
||||
calculateTotalToPay();
|
||||
|
||||
|
@ -236,7 +252,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
txFeeAsCoin,
|
||||
takerFeeAsCoin,
|
||||
tradePrice.getValue(),
|
||||
totalToPayAsCoin.get().subtract(takerFeeAsCoin),
|
||||
totalToPayAsCoin.get().subtract(takerFeeAsCoin).subtract(txFeeAsCoin),
|
||||
offer,
|
||||
paymentAccount.getId(),
|
||||
useSavingsWallet,
|
||||
|
@ -327,7 +343,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
// and reserved his funds, so that would not work well with dynamic fees.
|
||||
// The mining fee for the takeOfferFee tx is deducted from the createOfferFee and not visible to the trader
|
||||
if (offer != null && amountAsCoin.get() != null) {
|
||||
Coin value = takerFeeAsCoin.add(doubleTxFeeAsCoin).add(securityDepositAsCoin);
|
||||
Coin value = takerFeeAsCoin.add(totalTxFeeAsCoin).add(securityDepositAsCoin);
|
||||
if (getDirection() == Offer.Direction.SELL)
|
||||
totalToPayAsCoin.set(value);
|
||||
else
|
||||
|
@ -403,7 +419,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
//noinspection SimplifiableIfStatement
|
||||
if (amountAsCoin.get() != null && offer != null) {
|
||||
Coin customAmount = offer.getAmount().subtract(amountAsCoin.get());
|
||||
Coin dustAndFee = doubleTxFeeAsCoin.add(Transaction.MIN_NONDUST_OUTPUT);
|
||||
Coin dustAndFee = totalTxFeeAsCoin.add(Transaction.MIN_NONDUST_OUTPUT);
|
||||
return customAmount.isPositive() && customAmount.isLessThan(dustAndFee);
|
||||
} else {
|
||||
return true;
|
||||
|
@ -430,8 +446,8 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
return takerFeeAsCoin;
|
||||
}
|
||||
|
||||
public Coin getTxFeeAsCoin() {
|
||||
return doubleTxFeeAsCoin;
|
||||
public Coin getTotalTxFeeAsCoin() {
|
||||
return totalTxFeeAsCoin;
|
||||
}
|
||||
|
||||
public AddressEntry getAddressEntry() {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package io.bitsquare.gui.main.offer.takeoffer;
|
||||
|
||||
import io.bitsquare.arbitration.Arbitrator;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
|
||||
import io.bitsquare.gui.common.model.ViewModel;
|
||||
|
@ -578,7 +578,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
}
|
||||
|
||||
String getNetworkFee() {
|
||||
return formatter.formatCoinWithCode(dataModel.getTxFeeAsCoin());
|
||||
return formatter.formatCoinWithCode(dataModel.getTotalTxFeeAsCoin());
|
||||
}
|
||||
|
||||
public String getSecurityDeposit() {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package io.bitsquare.gui.main.portfolio.openoffer;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||
import io.bitsquare.common.handlers.ResultHandler;
|
||||
import io.bitsquare.gui.common.model.ActivatableDataModel;
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package io.bitsquare.gui.main.portfolio.openoffer;
|
||||
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
|
@ -112,7 +111,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
String key = "RemoveOfferWarning";
|
||||
if (preferences.showAgain(key))
|
||||
new Popup().warning("Are you sure you want to remove that offer?\n" +
|
||||
"The offer fee of " + model.formatter.formatCoinWithCode(FeePolicy.getCreateOfferFee()) + " will be lost if you remove that offer.")
|
||||
"The offer fee of " + model.formatter.formatCoinWithCode(openOffer.getOffer().getCreateOfferFee()) + " will be lost if you remove that offer.")
|
||||
.actionButtonText("Remove offer")
|
||||
.onAction(() -> doRemoveOpenOffer(openOffer))
|
||||
.closeButtonText("Don't remove the offer")
|
||||
|
|
|
@ -22,10 +22,9 @@ import io.bitsquare.app.Log;
|
|||
import io.bitsquare.arbitration.Arbitrator;
|
||||
import io.bitsquare.arbitration.Dispute;
|
||||
import io.bitsquare.arbitration.DisputeManager;
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.btc.FeeService;
|
||||
import io.bitsquare.btc.TradeWalletService;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.common.crypto.KeyRing;
|
||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||
import io.bitsquare.common.handlers.FaultHandler;
|
||||
|
@ -218,7 +217,18 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
}
|
||||
|
||||
Coin getTotalFees() {
|
||||
return feeService.getTxFee().add(isOfferer() ? FeePolicy.getCreateOfferFee() : FeePolicy.getTakeOfferFee());
|
||||
Trade trade = getTrade();
|
||||
if (trade != null) {
|
||||
if (isOfferer()) {
|
||||
Offer offer = trade.getOffer();
|
||||
return offer.getCreateOfferFee().add(offer.getTxFee());
|
||||
} else {
|
||||
return trade.getTakeOfferFee().add(trade.getTxFee().multiply(3));
|
||||
}
|
||||
} else {
|
||||
log.error("Trade is null at getTotalFees");
|
||||
return Coin.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
public String getCurrencyCode() {
|
||||
|
|
|
@ -47,6 +47,7 @@ public class HttpClient {
|
|||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
log.info("baseUrl for HttpClient: " + baseUrl);
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
|
@ -54,7 +55,6 @@ public class HttpClient {
|
|||
this.ignoreSocks5Proxy = ignoreSocks5Proxy;
|
||||
}
|
||||
|
||||
|
||||
public String requestWithGET(String param, @Nullable String headerKey, @Nullable String headerValue) throws IOException, HttpException {
|
||||
checkNotNull(baseUrl, "baseUrl must be set before calling requestWithGET");
|
||||
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -49,7 +49,7 @@
|
|||
<module>seednode</module>
|
||||
<module>monitor</module>
|
||||
<module>statistics</module>
|
||||
<module>pricefeed</module>
|
||||
<module>provider</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>pricefeed</artifactId>
|
||||
<artifactId>provider</artifactId>
|
||||
|
||||
<build>
|
||||
|
||||
|
@ -42,7 +42,7 @@
|
|||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>io.bitsquare.pricefeed.PriceFeedMain</mainClass>
|
||||
<mainClass>io.bitsquare.provider.ProviderMain</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<filters>
|
||||
|
@ -66,7 +66,7 @@
|
|||
<configuration>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<shadedClassifierName>bundled</shadedClassifierName>
|
||||
<finalName>Pricefeed</finalName>
|
||||
<finalName>provider</finalName>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
|
@ -15,9 +15,11 @@
|
|||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.pricefeed;
|
||||
package io.bitsquare.provider;
|
||||
|
||||
import io.bitsquare.http.HttpException;
|
||||
import io.bitsquare.provider.fee.FeeRequestService;
|
||||
import io.bitsquare.provider.price.PriceRequestService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -30,22 +32,39 @@ import java.security.spec.InvalidKeySpecException;
|
|||
import static spark.Spark.get;
|
||||
import static spark.Spark.port;
|
||||
|
||||
public class PriceFeedMain {
|
||||
private static final Logger log = LoggerFactory.getLogger(PriceFeedMain.class);
|
||||
public class ProviderMain {
|
||||
private static final Logger log = LoggerFactory.getLogger(ProviderMain.class);
|
||||
|
||||
public ProviderMain() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, InvalidKeyException, HttpException {
|
||||
port(8080);
|
||||
|
||||
handleGetAllMarketPrices(args);
|
||||
handleGetFees();
|
||||
}
|
||||
|
||||
private static void handleGetAllMarketPrices(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
|
||||
if (args.length == 2) {
|
||||
String bitcoinAveragePrivKey = args[0];
|
||||
String bitcoinAveragePubKey = args[1];
|
||||
|
||||
PriceRequestService priceRequestService = new PriceRequestService(bitcoinAveragePrivKey, bitcoinAveragePubKey);
|
||||
port(8080);
|
||||
get("/all", (req, res) -> {
|
||||
log.info("Incoming request from: " + req.userAgent());
|
||||
get("/getAllMarketPrices", (req, res) -> {
|
||||
log.info("Incoming getAllMarketPrices request from: " + req.userAgent());
|
||||
return priceRequestService.getJson();
|
||||
});
|
||||
} else {
|
||||
throw new IllegalArgumentException("You need to provide the BitcoinAverage API keys. Private key as first argument, public key as second argument.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleGetFees() throws IOException {
|
||||
FeeRequestService feeRequestService = new FeeRequestService();
|
||||
get("/getFees", (req, res) -> {
|
||||
log.info("Incoming getFees request from: " + req.userAgent());
|
||||
return feeRequestService.getJson();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.provider.fee;
|
||||
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.common.util.Utilities;
|
||||
import io.bitsquare.http.HttpException;
|
||||
import io.bitsquare.provider.fee.providers.BitcoinFeesProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class FeeRequestService {
|
||||
private static final Logger log = LoggerFactory.getLogger(FeeRequestService.class);
|
||||
|
||||
private static final long INTERVAL_BTC_FEES_MS = 600_000; // 10 min
|
||||
|
||||
private final Timer timerBitcoinFeesLocal = new Timer();
|
||||
|
||||
private final BitcoinFeesProvider bitcoinFeesProvider;
|
||||
private final Map<String, Long> allFeesMap = new ConcurrentHashMap<>();
|
||||
private long bitcoinFeesTs;
|
||||
private String json;
|
||||
|
||||
public FeeRequestService() throws IOException {
|
||||
bitcoinFeesProvider = new BitcoinFeesProvider();
|
||||
|
||||
allFeesMap.put("txFee", FeeService.DEFAULT_TX_FEE);
|
||||
allFeesMap.put("createOfferFee", FeeService.DEFAULT_CREATE_OFFER_FEE);
|
||||
allFeesMap.put("takeOfferFee", FeeService.DEFAULT_TAKE_OFFER_FEE);
|
||||
|
||||
startRequests();
|
||||
}
|
||||
|
||||
private void startRequests() throws IOException {
|
||||
timerBitcoinFeesLocal.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
requestBitcoinFeesLocalPrices();
|
||||
} catch (HttpException | IOException e) {
|
||||
log.warn(e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}, INTERVAL_BTC_FEES_MS, INTERVAL_BTC_FEES_MS);
|
||||
|
||||
|
||||
try {
|
||||
requestBitcoinFeesLocalPrices();
|
||||
} catch (HttpException e) {
|
||||
log.warn(e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void requestBitcoinFeesLocalPrices() throws IOException, HttpException {
|
||||
long ts = System.currentTimeMillis();
|
||||
long result = bitcoinFeesProvider.getFee();
|
||||
// log.info("requestBitcoinFeesLocalPrices took {} ms.", (System.currentTimeMillis() - ts));
|
||||
if (result < FeeService.MIN_TX_FEE) {
|
||||
log.warn("Response for fee is lower as min fee. Fee=" + result);
|
||||
} else if (result > FeeService.MAX_TX_FEE) {
|
||||
log.warn("Response for fee is larger as max fee. Fee=" + result);
|
||||
} else {
|
||||
bitcoinFeesTs = Instant.now().getEpochSecond();
|
||||
allFeesMap.put("txFee", result);
|
||||
writeToJson();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeToJson() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("bitcoinFeesTs", bitcoinFeesTs);
|
||||
map.put("data", allFeesMap);
|
||||
json = Utilities.objectToJson(map);
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.pricefeed;
|
||||
package io.bitsquare.provider.price;
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
public class PriceData {
|
|
@ -15,13 +15,13 @@
|
|||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.pricefeed;
|
||||
package io.bitsquare.provider.price;
|
||||
|
||||
import io.bitsquare.common.util.Utilities;
|
||||
import io.bitsquare.http.HttpException;
|
||||
import io.bitsquare.pricefeed.providers.BtcAverageProvider;
|
||||
import io.bitsquare.pricefeed.providers.CoinmarketcapProvider;
|
||||
import io.bitsquare.pricefeed.providers.PoloniexProvider;
|
||||
import io.bitsquare.provider.price.providers.BtcAverageProvider;
|
||||
import io.bitsquare.provider.price.providers.CoinmarketcapProvider;
|
||||
import io.bitsquare.provider.price.providers.PoloniexProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -35,7 +35,7 @@ import java.util.Timer;
|
|||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
class PriceRequestService {
|
||||
public class PriceRequestService {
|
||||
private static final Logger log = LoggerFactory.getLogger(PriceRequestService.class);
|
||||
|
||||
private static final long INTERVAL_BTC_AV_LOCAL_MS = 60_000; // 60 sec
|
||||
|
@ -142,7 +142,9 @@ class PriceRequestService {
|
|||
|
||||
|
||||
private void requestCoinmarketcapPrices() throws IOException, HttpException {
|
||||
long ts = System.currentTimeMillis();
|
||||
Map<String, PriceData> map = coinmarketcapProvider.request();
|
||||
log.info("requestCoinmarketcapPrices took {} ms.", (System.currentTimeMillis() - ts));
|
||||
// we don't replace prices which we got form the Poloniex request, just in case the Coinmarketcap data are
|
||||
// received earlier at startup we allow them but Poloniex will overwrite them.
|
||||
map.entrySet().stream()
|
||||
|
@ -154,21 +156,27 @@ class PriceRequestService {
|
|||
|
||||
|
||||
private void requestPoloniexPrices() throws IOException, HttpException {
|
||||
long ts = System.currentTimeMillis();
|
||||
poloniexMap = poloniexProvider.request();
|
||||
log.info("requestPoloniexPrices took {} ms.", (System.currentTimeMillis() - ts));
|
||||
allPricesMap.putAll(poloniexMap);
|
||||
poloniexTs = Instant.now().getEpochSecond();
|
||||
writeToJson();
|
||||
}
|
||||
|
||||
private void requestBtcAverageLocalPrices() throws NoSuchAlgorithmException, InvalidKeyException, IOException, HttpException {
|
||||
long ts = System.currentTimeMillis();
|
||||
btcAverageLocalMap = btcAverageProvider.getLocal();
|
||||
log.info("requestBtcAverageLocalPrices took {} ms.", (System.currentTimeMillis() - ts));
|
||||
allPricesMap.putAll(btcAverageLocalMap);
|
||||
btcAverageTs = Instant.now().getEpochSecond();
|
||||
writeToJson();
|
||||
}
|
||||
|
||||
private void requestBtcAverageGlobalPrices() throws NoSuchAlgorithmException, InvalidKeyException, IOException, HttpException {
|
||||
long ts = System.currentTimeMillis();
|
||||
Map<String, PriceData> map = btcAverageProvider.getGlobal();
|
||||
log.info("requestBtcAverageGlobalPrices took {} ms.", (System.currentTimeMillis() - ts));
|
||||
// we don't replace prices which we got form the local request, just in case the global data are received
|
||||
// earlier at startup we allow them but the local request will overwrite them.
|
||||
map.entrySet().stream()
|
|
@ -15,13 +15,13 @@
|
|||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.pricefeed.providers;
|
||||
package io.bitsquare.provider.price.providers;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
import io.bitsquare.http.HttpClient;
|
||||
import io.bitsquare.http.HttpException;
|
||||
import io.bitsquare.pricefeed.PriceData;
|
||||
import io.bitsquare.provider.price.PriceData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
|
@ -1,4 +1,4 @@
|
|||
package io.bitsquare.pricefeed.providers;
|
||||
package io.bitsquare.provider.price.providers;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
|
@ -6,7 +6,7 @@ import io.bitsquare.http.HttpClient;
|
|||
import io.bitsquare.http.HttpException;
|
||||
import io.bitsquare.locale.CurrencyUtil;
|
||||
import io.bitsquare.locale.TradeCurrency;
|
||||
import io.bitsquare.pricefeed.PriceData;
|
||||
import io.bitsquare.provider.price.PriceData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.bitsquare.pricefeed.providers;
|
||||
package io.bitsquare.provider.price.providers;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
|
@ -6,7 +6,7 @@ import io.bitsquare.http.HttpClient;
|
|||
import io.bitsquare.http.HttpException;
|
||||
import io.bitsquare.locale.CurrencyUtil;
|
||||
import io.bitsquare.locale.TradeCurrency;
|
||||
import io.bitsquare.pricefeed.PriceData;
|
||||
import io.bitsquare.provider.price.PriceData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -9,7 +9,7 @@ import io.bitsquare.app.Log;
|
|||
import io.bitsquare.app.Version;
|
||||
import io.bitsquare.arbitration.ArbitratorManager;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeedService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.common.CommonOptionKeys;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.handlers.ResultHandler;
|
||||
|
|
Loading…
Add table
Reference in a new issue