mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-20 10:22:18 +01:00
Merge branch 'DAO' into policyfile
This commit is contained in:
commit
f531659bf2
@ -28,6 +28,7 @@ import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -396,6 +397,21 @@ public class Utilities {
|
||||
Thread.currentThread().setName(name + "-" + new Random().nextInt(10000));
|
||||
}
|
||||
|
||||
public static Coin getFeePerBtc(Coin feePerBtc, Coin amount) {
|
||||
double feePerBtcAsDouble = (double) feePerBtc.value;
|
||||
double amountAsDouble = (double) amount.value;
|
||||
double btcAsDouble = (double) Coin.COIN.value;
|
||||
return Coin.valueOf(Math.round(feePerBtcAsDouble * (amountAsDouble / btcAsDouble)));
|
||||
}
|
||||
|
||||
public static Coin minCoin(Coin a, Coin b) {
|
||||
return a.compareTo(b) <= 0 ? a : b;
|
||||
}
|
||||
|
||||
public static Coin maxCoin(Coin a, Coin b) {
|
||||
return a.compareTo(b) >= 0 ? a : b;
|
||||
}
|
||||
|
||||
private static class AnnotationExclusionStrategy implements ExclusionStrategy {
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.common.util;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class UtilitiesTest {
|
||||
private static final Logger log = LoggerFactory.getLogger(UtilitiesTest.class);
|
||||
|
||||
@Test
|
||||
public void testGetFeePerBtc() {
|
||||
assertEquals(Coin.parseCoin("1"), Utilities.getFeePerBtc(Coin.parseCoin("1"), Coin.parseCoin("1")));
|
||||
assertEquals(Coin.parseCoin("0.1"), Utilities.getFeePerBtc(Coin.parseCoin("0.1"), Coin.parseCoin("1")));
|
||||
assertEquals(Coin.parseCoin("0.01"), Utilities.getFeePerBtc(Coin.parseCoin("0.1"), Coin.parseCoin("0.1")));
|
||||
assertEquals(Coin.parseCoin("0.015"), Utilities.getFeePerBtc(Coin.parseCoin("0.3"), Coin.parseCoin("0.05")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinCoin() {
|
||||
assertEquals(Coin.parseCoin("1"), Utilities.minCoin(Coin.parseCoin("1"), Coin.parseCoin("1")));
|
||||
assertEquals(Coin.parseCoin("0.1"), Utilities.minCoin(Coin.parseCoin("0.1"), Coin.parseCoin("1")));
|
||||
assertEquals(Coin.parseCoin("0.01"), Utilities.minCoin(Coin.parseCoin("0.1"), Coin.parseCoin("0.01")));
|
||||
assertEquals(Coin.parseCoin("0"), Utilities.minCoin(Coin.parseCoin("0"), Coin.parseCoin("0.05")));
|
||||
assertEquals(Coin.parseCoin("0"), Utilities.minCoin(Coin.parseCoin("0.05"), Coin.parseCoin("0")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxCoin() {
|
||||
assertEquals(Coin.parseCoin("1"), Utilities.maxCoin(Coin.parseCoin("1"), Coin.parseCoin("1")));
|
||||
assertEquals(Coin.parseCoin("1"), Utilities.maxCoin(Coin.parseCoin("0.1"), Coin.parseCoin("1")));
|
||||
assertEquals(Coin.parseCoin("0.1"), Utilities.maxCoin(Coin.parseCoin("0.1"), Coin.parseCoin("0.01")));
|
||||
assertEquals(Coin.parseCoin("0.05"), Utilities.maxCoin(Coin.parseCoin("0"), Coin.parseCoin("0.05")));
|
||||
assertEquals(Coin.parseCoin("0.05"), Utilities.maxCoin(Coin.parseCoin("0.05"), Coin.parseCoin("0")));
|
||||
}
|
||||
|
||||
}
|
@ -51,6 +51,7 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -92,13 +93,13 @@ public class DisputeManager {
|
||||
|
||||
@Inject
|
||||
public DisputeManager(P2PService p2PService,
|
||||
TradeWalletService tradeWalletService,
|
||||
BtcWalletService walletService,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
KeyRing keyRing,
|
||||
@Named(Storage.DIR_KEY) File storageDir) {
|
||||
TradeWalletService tradeWalletService,
|
||||
BtcWalletService walletService,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
KeyRing keyRing,
|
||||
@Named(Storage.DIR_KEY) File storageDir) {
|
||||
this.p2PService = p2PService;
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.walletService = walletService;
|
||||
@ -597,7 +598,8 @@ public class DisputeManager {
|
||||
if (dispute.getDepositTxSerialized() != null) {
|
||||
try {
|
||||
log.debug("do payout Transaction ");
|
||||
|
||||
byte[] multiSigPubKey = isBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
|
||||
DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(dispute.getTradeId(), multiSigPubKey);
|
||||
Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(
|
||||
dispute.getDepositTxSerialized(),
|
||||
disputeResult.getArbitratorSignature(),
|
||||
@ -607,9 +609,9 @@ public class DisputeManager {
|
||||
contract.getBuyerPayoutAddressString(),
|
||||
contract.getSellerPayoutAddressString(),
|
||||
disputeResult.getArbitratorAddressAsString(),
|
||||
walletService.getOrCreateAddressEntry(dispute.getTradeId(), AddressEntry.Context.MULTI_SIG),
|
||||
contract.getBuyerBtcPubKey(),
|
||||
contract.getSellerBtcPubKey(),
|
||||
multiSigKeyPair,
|
||||
contract.getBuyerMultiSigPubKey(),
|
||||
contract.getSellerMultiSigPubKey(),
|
||||
disputeResult.getArbitratorPubKey()
|
||||
);
|
||||
Transaction committedDisputedPayoutTx = tradeWalletService.addTransactionToWallet(signedDisputedPayoutTx);
|
||||
@ -686,8 +688,9 @@ public class DisputeManager {
|
||||
if (disputeOptional.isPresent()) {
|
||||
cleanupRetryMap(uid);
|
||||
|
||||
Transaction transaction = tradeWalletService.addTransactionToWallet(peerPublishedPayoutTxMessage.transaction);
|
||||
disputeOptional.get().setDisputePayoutTxId(transaction.getHashAsString());
|
||||
Transaction walletTx = tradeWalletService.addTransactionToWallet(peerPublishedPayoutTxMessage.transaction);
|
||||
disputeOptional.get().setDisputePayoutTxId(walletTx.getHashAsString());
|
||||
BtcWalletService.printTx("Disputed payoutTx received from peer", walletTx);
|
||||
tradeManager.closeDisputedTrade(tradeId);
|
||||
} else {
|
||||
log.debug("We got a peerPublishedPayoutTxMessage but we don't have a matching dispute. TradeId = " + tradeId);
|
||||
|
@ -73,7 +73,7 @@ public final class AddressEntry implements Persistable {
|
||||
private final byte[] pubKeyHash;
|
||||
private final String paramId;
|
||||
@Nullable
|
||||
private Coin lockedTradeAmount;
|
||||
private Coin coinLockedInMultiSig;
|
||||
transient private NetworkParameters params;
|
||||
|
||||
|
||||
@ -176,13 +176,13 @@ public final class AddressEntry implements Persistable {
|
||||
return isOpenOffer() || isTrade();
|
||||
}
|
||||
|
||||
public void setLockedTradeAmount(Coin lockedTradeAmount) {
|
||||
this.lockedTradeAmount = lockedTradeAmount;
|
||||
public void setCoinLockedInMultiSig(Coin coinLockedInMultiSig) {
|
||||
this.coinLockedInMultiSig = coinLockedInMultiSig;
|
||||
}
|
||||
|
||||
@org.jetbrains.annotations.Nullable
|
||||
public Coin getLockedTradeAmount() {
|
||||
return lockedTradeAmount;
|
||||
@Nullable
|
||||
public Coin getCoinLockedInMultiSig() {
|
||||
return coinLockedInMultiSig;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,6 +95,6 @@ public final class AddressEntryList extends ArrayList<AddressEntry> implements P
|
||||
}
|
||||
|
||||
public void queueUpForSave() {
|
||||
storage.queueUpForSave();
|
||||
storage.queueUpForSave(50);
|
||||
}
|
||||
}
|
||||
|
@ -31,28 +31,8 @@ public class FeePolicy {
|
||||
// https://estimatefee.appspot.com/
|
||||
// Average values are 10-100 satoshis/byte in january 2016
|
||||
// Average values are 60-140 satoshis/byte in february 2017
|
||||
//
|
||||
// Our trade transactions have a fixed set of inputs and outputs making the size very predictable
|
||||
// (as long the user does not do multiple funding transactions)
|
||||
//
|
||||
// trade fee tx: 226 bytes // 221 satoshi/byte
|
||||
// deposit tx: 336 bytes // 148 satoshi/byte
|
||||
// payout tx: 371 bytes // 134 satoshi/byte
|
||||
// disputed payout tx: 408 bytes // 122 satoshi/byte
|
||||
|
||||
// We set a fixed fee to make the needed amounts in the trade predictable.
|
||||
// We use 0.0005 BTC (0.5 EUR @ 1000 EUR/BTC) which is for our tx sizes about 120-220 satoshi/byte
|
||||
// We cannot make that user defined as it need to be the same for both users, so we can only change that in
|
||||
// software updates
|
||||
// TODO before Beta we should get a good future proof guess as a change causes incompatible versions
|
||||
|
||||
// For non trade transactions (withdrawal) we use the default fee calculation
|
||||
// To avoid issues with not getting into full blocks, we increase the fee/kb to 30 satoshi/byte
|
||||
// The user can change that in the preferences
|
||||
// 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 = Coin.valueOf(40_000); // 0.0004 BTC about 0.16 EUR @ 400 EUR/BTC
|
||||
private static Coin NON_TRADE_FEE_PER_KB = Coin.valueOf(150_000);
|
||||
|
||||
public static void setNonTradeFeePerKb(Coin nonTradeFeePerKb) {
|
||||
NON_TRADE_FEE_PER_KB = nonTradeFeePerKb;
|
||||
@ -62,4 +42,8 @@ public class FeePolicy {
|
||||
return NON_TRADE_FEE_PER_KB;
|
||||
}
|
||||
|
||||
public static Coin getDefaultSecurityDeposit() {
|
||||
return Coin.valueOf(3_000_000);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,12 +7,8 @@ public class FeeData {
|
||||
private static final Logger log = LoggerFactory.getLogger(FeeData.class);
|
||||
|
||||
public final long txFeePerByte;
|
||||
public final long createOfferFee;
|
||||
public final long takeOfferFee;
|
||||
|
||||
public FeeData(long txFeePerByte, long createOfferFee, long takeOfferFee) {
|
||||
public FeeData(long txFeePerByte) {
|
||||
this.txFeePerByte = txFeePerByte;
|
||||
this.createOfferFee = createOfferFee;
|
||||
this.takeOfferFee = takeOfferFee;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
//TODO use protobuffer instead of json
|
||||
public class FeeProvider extends HttpClientProvider {
|
||||
private static final Logger log = LoggerFactory.getLogger(FeeProvider.class);
|
||||
|
||||
@ -28,7 +29,7 @@ public class FeeProvider extends HttpClientProvider {
|
||||
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());
|
||||
FeeData feeData = new FeeData(dataMap.get("txFee").longValue());
|
||||
return new Tuple2<>(tsMap, feeData);
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,6 @@ 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;
|
||||
@ -33,6 +32,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
@ -40,22 +40,28 @@ 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 = 100;
|
||||
public static final long MIN_TX_FEE = 60; // satoshi/byte
|
||||
public static final long MAX_TX_FEE = 300;
|
||||
public static final long DEFAULT_TX_FEE = 150;
|
||||
|
||||
public static final long MIN_CREATE_OFFER_FEE = 10_000;
|
||||
public static final long MAX_CREATE_OFFER_FEE = 500_000;
|
||||
public static final long DEFAULT_CREATE_OFFER_FEE = 30_000;
|
||||
public static final long MIN_CREATE_OFFER_FEE_IN_BTC = 10_000;
|
||||
public static final long MAX_CREATE_OFFER_FEE_IN_BTC = 500_000;
|
||||
public static final long DEFAULT_CREATE_OFFER_FEE_IN_BTC_PER_BTC = 30_000; // excluded mining fee
|
||||
|
||||
public static final long MIN_TAKE_OFFER_FEE = 10_000;
|
||||
public static final long MAX_TAKE_OFFER_FEE = 1000_000;
|
||||
public static final long DEFAULT_TAKE_OFFER_FEE = 80_000;
|
||||
public static final long MIN_TAKE_OFFER_FEE_IN_BTC = 10_000;
|
||||
public static final long MAX_TAKE_OFFER_FEE_IN_BTC = 1000_000;
|
||||
public static final long DEFAULT_TAKE_OFFER_FEE_IN_BTC_PER_BTC = 40_000; // excluded mining fee
|
||||
|
||||
// 0.00216 btc is for 3 x tx fee for taker -> about 2 EUR!
|
||||
|
||||
public static final long MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN = 10;
|
||||
|
||||
private final FeeProvider feeProvider;
|
||||
@Nullable
|
||||
private FeeData feeData;
|
||||
private Map<String, Long> timeStampMap;
|
||||
private long epochInSecondAtLastRequest;
|
||||
private long lastRequest;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
@ -65,7 +71,6 @@ public class FeeService {
|
||||
public FeeService(HttpClient httpClient,
|
||||
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() {
|
||||
@ -73,30 +78,39 @@ public class FeeService {
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
long now = Instant.now().getEpochSecond();
|
||||
if (feeData == null || now - lastRequest > MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN * 60) {
|
||||
lastRequest = now;
|
||||
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;
|
||||
log.info("Tx fee: txFeePerByte=" + feeData.txFeePerByte);
|
||||
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));
|
||||
}
|
||||
});
|
||||
@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));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.debug("We got a requestFees called again before min pause of {} minutes has passed.", MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN);
|
||||
UserThread.execute(() -> {
|
||||
if (resultHandler != null)
|
||||
resultHandler.run();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Coin getTxFee(int sizeInBytes) {
|
||||
@ -104,16 +118,28 @@ public class FeeService {
|
||||
}
|
||||
|
||||
public Coin getTxFeePerByte() {
|
||||
log.info("txFeePerByte = " + feeData.txFeePerByte);
|
||||
return Coin.valueOf(feeData.txFeePerByte);
|
||||
if (feeData != null)
|
||||
return Coin.valueOf(feeData.txFeePerByte);
|
||||
else
|
||||
return Coin.valueOf(DEFAULT_TX_FEE);
|
||||
}
|
||||
|
||||
public Coin getCreateOfferFee() {
|
||||
return Coin.valueOf(feeData.createOfferFee);
|
||||
// TODO we will get that from the DAO voting
|
||||
public Coin getCreateOfferFeeInBtcPerBtc() {
|
||||
return Coin.valueOf(DEFAULT_CREATE_OFFER_FEE_IN_BTC_PER_BTC);
|
||||
}
|
||||
|
||||
public Coin getTakeOfferFee() {
|
||||
return Coin.valueOf(feeData.takeOfferFee);
|
||||
public Coin getMinCreateOfferFeeInBtc() {
|
||||
return Coin.valueOf(MIN_CREATE_OFFER_FEE_IN_BTC);
|
||||
}
|
||||
|
||||
public Coin getMinTakeOfferFeeInBtc() {
|
||||
return Coin.valueOf(MIN_TAKE_OFFER_FEE_IN_BTC);
|
||||
}
|
||||
|
||||
// TODO we will get that from the DAO voting
|
||||
public Coin getTakeOfferFeeInBtcPerBtc() {
|
||||
return Coin.valueOf(DEFAULT_TAKE_OFFER_FEE_IN_BTC_PER_BTC);
|
||||
}
|
||||
|
||||
public Coin getCreateCompensationRequestFee() {
|
||||
|
@ -37,10 +37,7 @@ import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
@ -282,25 +279,33 @@ public class BtcWalletService extends WalletService {
|
||||
// AddressEntry
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Optional<AddressEntry> getAddressEntry(String offerId, AddressEntry.Context context) {
|
||||
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> offerId.equals(e.getOfferId()))
|
||||
.filter(e -> context == e.getContext())
|
||||
.findAny();
|
||||
return addressEntry;
|
||||
}
|
||||
|
||||
public AddressEntry getOrCreateAddressEntry(String offerId, AddressEntry.Context context) {
|
||||
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> offerId.equals(e.getOfferId()))
|
||||
.filter(e -> context == e.getContext())
|
||||
.findAny();
|
||||
if (addressEntry.isPresent())
|
||||
if (addressEntry.isPresent()) {
|
||||
return addressEntry.get();
|
||||
else
|
||||
return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context, offerId));
|
||||
} else {
|
||||
AddressEntry entry = addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context, offerId));
|
||||
saveAddressEntryList();
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
public AddressEntry getOrCreateAddressEntry(AddressEntry.Context context) {
|
||||
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> context == e.getContext())
|
||||
.findAny();
|
||||
if (addressEntry.isPresent())
|
||||
return addressEntry.get();
|
||||
else
|
||||
return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context));
|
||||
return getOrCreateAddressEntry(context, addressEntry);
|
||||
}
|
||||
|
||||
public AddressEntry getOrCreateUnusedAddressEntry(AddressEntry.Context context) {
|
||||
@ -308,12 +313,20 @@ public class BtcWalletService extends WalletService {
|
||||
.filter(e -> context == e.getContext())
|
||||
.filter(e -> getNumTxOutputsForAddress(e.getAddress()) == 0)
|
||||
.findAny();
|
||||
if (addressEntry.isPresent())
|
||||
return addressEntry.get();
|
||||
else
|
||||
return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context));
|
||||
return getOrCreateAddressEntry(context, addressEntry);
|
||||
}
|
||||
|
||||
private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional<AddressEntry> addressEntry) {
|
||||
if (addressEntry.isPresent()) {
|
||||
return addressEntry.get();
|
||||
} else {
|
||||
AddressEntry entry = addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context));
|
||||
saveAddressEntryList();
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Optional<AddressEntry> findAddressEntry(String address, AddressEntry.Context context) {
|
||||
return getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> address.equals(e.getAddressString()))
|
||||
@ -348,7 +361,10 @@ public class BtcWalletService extends WalletService {
|
||||
.filter(e -> offerId.equals(e.getOfferId()))
|
||||
.filter(e -> context == e.getContext())
|
||||
.findAny();
|
||||
addressEntryOptional.ifPresent(addressEntryList::swapToAvailable);
|
||||
addressEntryOptional.ifPresent(e -> {
|
||||
addressEntryList.swapToAvailable(e);
|
||||
saveAddressEntryList();
|
||||
});
|
||||
}
|
||||
|
||||
public void swapAnyTradeEntryContextToAvailableEntry(String offerId) {
|
||||
@ -363,6 +379,27 @@ public class BtcWalletService extends WalletService {
|
||||
}
|
||||
|
||||
|
||||
public DeterministicKey getMultiSigKeyPair(String tradeId, byte[] pubKey) {
|
||||
Optional<AddressEntry> multiSigAddressEntryOptional = getAddressEntry(tradeId, AddressEntry.Context.MULTI_SIG);
|
||||
DeterministicKey multiSigKeyPair;
|
||||
if (multiSigAddressEntryOptional.isPresent()) {
|
||||
AddressEntry multiSigAddressEntry = multiSigAddressEntryOptional.get();
|
||||
multiSigKeyPair = multiSigAddressEntry.getKeyPair();
|
||||
if (!Arrays.equals(pubKey, multiSigAddressEntry.getPubKey())) {
|
||||
log.error("Pub Key from AddressEntry does not match key pair from trade data. Trade ID={}\n" +
|
||||
"We try to find the keypair in the wallet with the pubKey we found in the trade data.", tradeId);
|
||||
multiSigKeyPair = findKeyFromPubKeyHash(pubKey);
|
||||
}
|
||||
} else {
|
||||
log.error("multiSigAddressEntry not found for trade ID={}.\n" +
|
||||
"We try to find the keypair in the wallet with the pubKey we found in the trade data.", tradeId);
|
||||
multiSigKeyPair = findKeyFromPubKeyHash(pubKey);
|
||||
}
|
||||
|
||||
return multiSigKeyPair;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Balance
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -735,7 +772,6 @@ public class BtcWalletService extends WalletService {
|
||||
return sendRequest;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -190,21 +190,22 @@ public class TradeWalletService {
|
||||
|
||||
|
||||
/**
|
||||
* The taker creates a dummy transaction to get the input(s) and optional change output for the amount and the takersAddressEntry for that trade.
|
||||
* The taker creates a dummy transaction to get the input(s) and optional change output for the amount and the takersAddress for that trade.
|
||||
* That will be used to send to the offerer for creating the deposit transaction.
|
||||
*
|
||||
* @param inputAmount Amount of takers input
|
||||
* @param takersAddressEntry Address entry of taker
|
||||
* @param txFee Mining fee
|
||||
* @param takersAddress Address of taker
|
||||
* @return A data container holding the inputs, the output value and address
|
||||
* @throws TransactionVerificationException
|
||||
* @throws WalletException
|
||||
*/
|
||||
public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, Coin txFee, AddressEntry takersAddressEntry, Address takersChangeAddress) throws
|
||||
public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, Coin txFee, Address takersAddress, Address takersChangeAddress) throws
|
||||
TransactionVerificationException, WalletException {
|
||||
log.trace("takerCreatesDepositsTxInputs called");
|
||||
log.trace("inputAmount " + inputAmount.toFriendlyString());
|
||||
log.trace("txFee " + txFee.toFriendlyString());
|
||||
log.trace("takersAddressEntry " + takersAddressEntry.toString());
|
||||
log.trace("takersAddress " + takersAddress.toString());
|
||||
|
||||
// We add the mining fee 2 times to the deposit tx:
|
||||
// 1. Will be spent when publishing the deposit tx (paid by buyer)
|
||||
@ -222,6 +223,7 @@ public class TradeWalletService {
|
||||
We are only interested in the inputs and the optional change output.
|
||||
*/
|
||||
|
||||
|
||||
// inputAmount includes the tx fee. So we subtract the fee to get the dummyOutputAmount.
|
||||
Coin dummyOutputAmount = inputAmount.subtract(txFee);
|
||||
|
||||
@ -234,7 +236,7 @@ public class TradeWalletService {
|
||||
// Find the needed inputs to pay the output, optionally add 1 change output.
|
||||
// Normally only 1 input and no change output is used, but we support multiple inputs and 1 change output.
|
||||
// Our spending transaction output is from the create offer fee payment.
|
||||
addAvailableInputsAndChangeOutputs(dummyTX, takersAddressEntry, takersChangeAddress, txFee);
|
||||
addAvailableInputsAndChangeOutputs(dummyTX, takersAddress, takersChangeAddress, txFee);
|
||||
|
||||
// The completeTx() call signs the input, but we don't want to pass over signed tx inputs so we remove the signature
|
||||
removeSignatures(dummyTX);
|
||||
@ -278,7 +280,8 @@ public class TradeWalletService {
|
||||
* @param takerRawTransactionInputs Raw data for the connected outputs for all inputs of the taker (normally 1 input)
|
||||
* @param takerChangeOutputValue Optional taker change output value
|
||||
* @param takerChangeAddressString Optional taker change address
|
||||
* @param offererAddressEntry The offerer's address entry.
|
||||
* @param offererAddress The offerer's address.
|
||||
* @param offererChangeAddress The offerer's change address.
|
||||
* @param buyerPubKey The public key of the buyer.
|
||||
* @param sellerPubKey The public key of the seller.
|
||||
* @param arbitratorPubKey The public key of the arbitrator.
|
||||
@ -294,7 +297,7 @@ public class TradeWalletService {
|
||||
List<RawTransactionInput> takerRawTransactionInputs,
|
||||
long takerChangeOutputValue,
|
||||
@Nullable String takerChangeAddressString,
|
||||
AddressEntry offererAddressEntry,
|
||||
Address offererAddress,
|
||||
Address offererChangeAddress,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey,
|
||||
@ -307,9 +310,11 @@ public class TradeWalletService {
|
||||
log.trace("takerRawInputs " + takerRawTransactionInputs.toString());
|
||||
log.trace("takerChangeOutputValue " + takerChangeOutputValue);
|
||||
log.trace("takerChangeAddressString " + takerChangeAddressString);
|
||||
log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.trace("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.trace("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
log.trace("offererAddress " + offererAddress);
|
||||
log.trace("offererChangeAddress " + offererChangeAddress);
|
||||
log.info("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.info("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.info("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
|
||||
checkArgument(!takerRawTransactionInputs.isEmpty());
|
||||
|
||||
@ -318,7 +323,7 @@ public class TradeWalletService {
|
||||
Transaction dummyTx = new Transaction(params);
|
||||
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, offererInputAmount, new ECKey().toAddress(params));
|
||||
dummyTx.addOutput(dummyOutput);
|
||||
addAvailableInputsAndChangeOutputs(dummyTx, offererAddressEntry, offererChangeAddress, Coin.ZERO);
|
||||
addAvailableInputsAndChangeOutputs(dummyTx, offererAddress, offererChangeAddress, Coin.ZERO);
|
||||
// Normally we have only 1 input but we support multiple inputs if the user has paid in with several transactions.
|
||||
List<TransactionInput> offererInputs = dummyTx.getInputs();
|
||||
TransactionOutput offererOutput = null;
|
||||
@ -407,9 +412,9 @@ public class TradeWalletService {
|
||||
checkScriptSig(preparedDepositTx, input, i);
|
||||
}
|
||||
|
||||
verifyTransaction(preparedDepositTx);
|
||||
BtcWalletService.printTx("prepared depositTx", preparedDepositTx);
|
||||
|
||||
//BtcWalletService.printTx("preparedDepositTx", preparedDepositTx);
|
||||
verifyTransaction(preparedDepositTx);
|
||||
|
||||
return new PreparedDepositTxAndOffererInputs(offererRawTransactionInputs, preparedDepositTx.bitcoinSerialize());
|
||||
}
|
||||
@ -447,9 +452,9 @@ public class TradeWalletService {
|
||||
log.trace("offerersDepositTx " + offerersDepositTx.toString());
|
||||
log.trace("buyerConnectedOutputsForAllInputs " + buyerInputs.toString());
|
||||
log.trace("sellerConnectedOutputsForAllInputs " + sellerInputs.toString());
|
||||
log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.trace("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.trace("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
log.info("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.info("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.info("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
|
||||
checkArgument(!buyerInputs.isEmpty());
|
||||
checkArgument(!sellerInputs.isEmpty());
|
||||
@ -506,11 +511,11 @@ public class TradeWalletService {
|
||||
checkScriptSig(depositTx, input, i);
|
||||
}
|
||||
|
||||
BtcWalletService.printTx("depositTx", depositTx);
|
||||
|
||||
verifyTransaction(depositTx);
|
||||
checkWalletConsistency();
|
||||
|
||||
BtcWalletService.printTx("depositTx", depositTx);
|
||||
|
||||
// Broadcast depositTx
|
||||
checkNotNull(walletConfig);
|
||||
ListenableFuture<Transaction> broadcastComplete = walletConfig.peerGroup().broadcastTransaction(depositTx).future();
|
||||
@ -527,7 +532,8 @@ public class TradeWalletService {
|
||||
* @param buyerPayoutAmount Payout amount for buyer
|
||||
* @param sellerPayoutAmount Payout amount for seller
|
||||
* @param buyerPayoutAddressString Address for buyer
|
||||
* @param sellerPayoutAddressEntry AddressEntry for seller
|
||||
* @param sellerPayoutAddressString Address for seller
|
||||
* @param multiSigKeyPair DeterministicKey for MultiSig from seller
|
||||
* @param lockTime Lock time
|
||||
* @param buyerPubKey The public key of the buyer.
|
||||
* @param sellerPubKey The public key of the seller.
|
||||
@ -540,8 +546,8 @@ public class TradeWalletService {
|
||||
Coin buyerPayoutAmount,
|
||||
Coin sellerPayoutAmount,
|
||||
String buyerPayoutAddressString,
|
||||
AddressEntry sellerPayoutAddressEntry,
|
||||
AddressEntry multiSigAddressEntry,
|
||||
String sellerPayoutAddressString,
|
||||
DeterministicKey multiSigKeyPair,
|
||||
long lockTime,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey,
|
||||
@ -552,51 +558,50 @@ public class TradeWalletService {
|
||||
log.trace("buyerPayoutAmount " + buyerPayoutAmount.toFriendlyString());
|
||||
log.trace("sellerPayoutAmount " + sellerPayoutAmount.toFriendlyString());
|
||||
log.trace("buyerPayoutAddressString " + buyerPayoutAddressString);
|
||||
log.trace("sellerPayoutAddressEntry " + sellerPayoutAddressEntry.toString());
|
||||
log.trace("multiSigAddressEntry " + multiSigAddressEntry.toString());
|
||||
log.trace("sellerPayoutAddressString " + sellerPayoutAddressString);
|
||||
log.trace("multiSigKeyPair (not displayed for security reasons)");
|
||||
log.trace("lockTime " + lockTime);
|
||||
log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.trace("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.trace("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
|
||||
log.info("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.info("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.info("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
Transaction preparedPayoutTx = createPayoutTx(depositTx,
|
||||
buyerPayoutAmount,
|
||||
sellerPayoutAmount,
|
||||
buyerPayoutAddressString,
|
||||
sellerPayoutAddressEntry.getAddressString(),
|
||||
sellerPayoutAddressString,
|
||||
lockTime
|
||||
);
|
||||
// MS redeemScript
|
||||
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
|
||||
// MS output from prev. tx is index 0
|
||||
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
DeterministicKey keyPair = multiSigAddressEntry.getKeyPair();
|
||||
checkNotNull(keyPair, "multiSigAddressEntry.getKeyPair() must not be null");
|
||||
if (keyPair.isEncrypted())
|
||||
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
|
||||
if (multiSigKeyPair.isEncrypted())
|
||||
checkNotNull(aesKey);
|
||||
|
||||
ECKey.ECDSASignature sellerSignature = keyPair.sign(sigHash, aesKey).toCanonicalised();
|
||||
ECKey.ECDSASignature sellerSignature = multiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
|
||||
|
||||
BtcWalletService.printTx("prepared payoutTx", preparedPayoutTx);
|
||||
|
||||
verifyTransaction(preparedPayoutTx);
|
||||
|
||||
//BtcWalletService.printTx("preparedPayoutTx", preparedPayoutTx);
|
||||
|
||||
return sellerSignature.encodeToDER();
|
||||
}
|
||||
|
||||
/**
|
||||
* Buyer creates and signs payout transaction and adds signature of seller to complete the transaction
|
||||
*
|
||||
* @param depositTx Deposit transaction
|
||||
* @param sellerSignature DER encoded canonical signature of seller
|
||||
* @param buyerPayoutAmount Payout amount for buyer
|
||||
* @param sellerPayoutAmount Payout amount for seller
|
||||
* @param buyerPayoutAddressEntry AddressEntry for buyer
|
||||
* @param sellerAddressString Address for seller
|
||||
* @param lockTime Lock time
|
||||
* @param buyerPubKey The public key of the buyer.
|
||||
* @param sellerPubKey The public key of the seller.
|
||||
* @param arbitratorPubKey The public key of the arbitrator.
|
||||
* @param depositTx Deposit transaction
|
||||
* @param sellerSignature DER encoded canonical signature of seller
|
||||
* @param buyerPayoutAmount Payout amount for buyer
|
||||
* @param sellerPayoutAmount Payout amount for seller
|
||||
* @param buyerPayoutAddressString Address for buyer
|
||||
* @param sellerPayoutAddressString Address for seller
|
||||
* @param multiSigKeyPair Buyer's keypair for MultiSig
|
||||
* @param lockTime Lock time
|
||||
* @param buyerPubKey The public key of the buyer.
|
||||
* @param sellerPubKey The public key of the seller.
|
||||
* @param arbitratorPubKey The public key of the arbitrator.
|
||||
* @return The payout transaction
|
||||
* @throws AddressFormatException
|
||||
* @throws TransactionVerificationException
|
||||
@ -606,9 +611,9 @@ public class TradeWalletService {
|
||||
byte[] sellerSignature,
|
||||
Coin buyerPayoutAmount,
|
||||
Coin sellerPayoutAmount,
|
||||
AddressEntry buyerPayoutAddressEntry,
|
||||
AddressEntry multiSigAddressEntry,
|
||||
String sellerAddressString,
|
||||
String buyerPayoutAddressString,
|
||||
String sellerPayoutAddressString,
|
||||
DeterministicKey multiSigKeyPair,
|
||||
long lockTime,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey,
|
||||
@ -620,30 +625,29 @@ public class TradeWalletService {
|
||||
log.trace("sellerSignature s " + ECKey.ECDSASignature.decodeFromDER(sellerSignature).s.toString());
|
||||
log.trace("buyerPayoutAmount " + buyerPayoutAmount.toFriendlyString());
|
||||
log.trace("sellerPayoutAmount " + sellerPayoutAmount.toFriendlyString());
|
||||
log.trace("buyerPayoutAddressEntry " + buyerPayoutAddressEntry);
|
||||
log.trace("multiSigAddressEntry " + multiSigAddressEntry);
|
||||
log.trace("sellerAddressString " + sellerAddressString);
|
||||
log.trace("buyerPayoutAddressString " + buyerPayoutAddressString);
|
||||
log.trace("sellerPayoutAddressString " + sellerPayoutAddressString);
|
||||
log.trace("multiSigKeyPair (not displayed for security reasons)");
|
||||
log.trace("lockTime " + lockTime);
|
||||
log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.trace("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.trace("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
log.info("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.info("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.info("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
|
||||
Transaction payoutTx = createPayoutTx(depositTx,
|
||||
buyerPayoutAmount,
|
||||
sellerPayoutAmount,
|
||||
buyerPayoutAddressEntry.getAddressString(),
|
||||
sellerAddressString,
|
||||
buyerPayoutAddressString,
|
||||
sellerPayoutAddressString,
|
||||
lockTime);
|
||||
// MS redeemScript
|
||||
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
|
||||
// MS output from prev. tx is index 0
|
||||
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
final DeterministicKey keyPair = multiSigAddressEntry.getKeyPair();
|
||||
checkNotNull(keyPair, "multiSigAddressEntry.getKeyPair() must not be null");
|
||||
if (keyPair.isEncrypted())
|
||||
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
|
||||
if (multiSigKeyPair.isEncrypted())
|
||||
checkNotNull(aesKey);
|
||||
|
||||
ECKey.ECDSASignature buyerSignature = keyPair.sign(sigHash, aesKey).toCanonicalised();
|
||||
ECKey.ECDSASignature buyerSignature = multiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
|
||||
|
||||
TransactionSignature sellerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(sellerSignature), Transaction.SigHash.ALL, false);
|
||||
TransactionSignature buyerTxSig = new TransactionSignature(buyerSignature, Transaction.SigHash.ALL, false);
|
||||
@ -679,7 +683,8 @@ public class TradeWalletService {
|
||||
* @param arbitratorPayoutAmount The payout amount of the arbitrator.
|
||||
* @param buyerAddressString The address of the buyer.
|
||||
* @param sellerAddressString The address of the seller.
|
||||
* @param arbitratorAddressEntry The addressEntry of the arbitrator.
|
||||
* @param arbitratorAddressString The address of the arbitrator.
|
||||
* @param arbitratorKeyPair The keypair of the arbitrator.
|
||||
* @param buyerPubKey The public key of the buyer.
|
||||
* @param sellerPubKey The public key of the seller.
|
||||
* @param arbitratorPubKey The public key of the arbitrator.
|
||||
@ -693,7 +698,8 @@ public class TradeWalletService {
|
||||
Coin arbitratorPayoutAmount,
|
||||
String buyerAddressString,
|
||||
String sellerAddressString,
|
||||
AddressEntry arbitratorAddressEntry,
|
||||
String arbitratorAddressString,
|
||||
DeterministicKey arbitratorKeyPair,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey,
|
||||
byte[] arbitratorPubKey)
|
||||
@ -706,10 +712,11 @@ public class TradeWalletService {
|
||||
log.trace("arbitratorPayoutAmount " + arbitratorPayoutAmount.toFriendlyString());
|
||||
log.trace("buyerAddressString " + buyerAddressString);
|
||||
log.trace("sellerAddressString " + sellerAddressString);
|
||||
log.trace("arbitratorAddressEntry " + arbitratorAddressEntry.toString());
|
||||
log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.trace("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.trace("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
log.trace("arbitratorAddressString " + arbitratorAddressString);
|
||||
log.trace("arbitratorKeyPair (not displayed for security reasons)");
|
||||
log.info("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.info("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.info("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
|
||||
// Our MS is index 0
|
||||
TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
|
||||
@ -719,18 +726,17 @@ public class TradeWalletService {
|
||||
preparedPayoutTx.addOutput(buyerPayoutAmount, new Address(params, buyerAddressString));
|
||||
if (sellerPayoutAmount.isGreaterThan(Coin.ZERO))
|
||||
preparedPayoutTx.addOutput(sellerPayoutAmount, new Address(params, sellerAddressString));
|
||||
if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO) && arbitratorAddressEntry.getAddressString() != null)
|
||||
preparedPayoutTx.addOutput(arbitratorPayoutAmount, new Address(params, arbitratorAddressEntry.getAddressString()));
|
||||
if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO) && arbitratorAddressString != null)
|
||||
preparedPayoutTx.addOutput(arbitratorPayoutAmount, new Address(params, arbitratorAddressString));
|
||||
|
||||
// take care of sorting!
|
||||
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
|
||||
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
final DeterministicKey keyPair = arbitratorAddressEntry.getKeyPair();
|
||||
checkNotNull(keyPair, "arbitratorAddressEntry.getKeyPair() must not be null");
|
||||
if (keyPair.isEncrypted())
|
||||
checkNotNull(arbitratorKeyPair, "arbitratorKeyPair must not be null");
|
||||
if (arbitratorKeyPair.isEncrypted())
|
||||
checkNotNull(aesKey);
|
||||
|
||||
ECKey.ECDSASignature arbitratorSignature = keyPair.sign(sigHash, aesKey).toCanonicalised();
|
||||
ECKey.ECDSASignature arbitratorSignature = arbitratorKeyPair.sign(sigHash, aesKey).toCanonicalised();
|
||||
|
||||
verifyTransaction(preparedPayoutTx);
|
||||
|
||||
@ -750,7 +756,7 @@ public class TradeWalletService {
|
||||
* @param buyerAddressString The address of the buyer.
|
||||
* @param sellerAddressString The address of the seller.
|
||||
* @param arbitratorAddressString The address of the arbitrator.
|
||||
* @param tradersMultiSigAddressEntry The addressEntry of the trader who calls that method
|
||||
* @param tradersMultiSigKeyPair The keypair for the MultiSig of the trader who calls that method
|
||||
* @param buyerPubKey The public key of the buyer.
|
||||
* @param sellerPubKey The public key of the seller.
|
||||
* @param arbitratorPubKey The public key of the arbitrator.
|
||||
@ -767,7 +773,7 @@ public class TradeWalletService {
|
||||
String buyerAddressString,
|
||||
String sellerAddressString,
|
||||
String arbitratorAddressString,
|
||||
AddressEntry tradersMultiSigAddressEntry,
|
||||
DeterministicKey tradersMultiSigKeyPair,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey,
|
||||
byte[] arbitratorPubKey)
|
||||
@ -784,10 +790,10 @@ public class TradeWalletService {
|
||||
log.trace("buyerAddressString " + buyerAddressString);
|
||||
log.trace("sellerAddressString " + sellerAddressString);
|
||||
log.trace("arbitratorAddressString " + arbitratorAddressString);
|
||||
log.trace("tradersMultiSigAddressEntry " + tradersMultiSigAddressEntry);
|
||||
log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.trace("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.trace("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
log.trace("tradersMultiSigKeyPair (not displayed for security reasons)");
|
||||
log.info("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
|
||||
log.info("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
|
||||
log.info("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
|
||||
|
||||
|
||||
TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
|
||||
@ -803,11 +809,10 @@ public class TradeWalletService {
|
||||
// take care of sorting!
|
||||
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
|
||||
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
DeterministicKey keyPair = tradersMultiSigAddressEntry.getKeyPair();
|
||||
checkNotNull(keyPair, "tradersMultiSigAddressEntry.getKeyPair() must not be null");
|
||||
if (keyPair.isEncrypted())
|
||||
checkNotNull(tradersMultiSigKeyPair, "tradersMultiSigKeyPair must not be null");
|
||||
if (tradersMultiSigKeyPair.isEncrypted())
|
||||
checkNotNull(aesKey);
|
||||
ECKey.ECDSASignature tradersSignature = keyPair.sign(sigHash, aesKey).toCanonicalised();
|
||||
ECKey.ECDSASignature tradersSignature = tradersMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
|
||||
|
||||
TransactionSignature tradersTxSig = new TransactionSignature(tradersSignature, Transaction.SigHash.ALL, false);
|
||||
TransactionSignature arbitratorTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(arbitratorSignature),
|
||||
@ -817,14 +822,14 @@ public class TradeWalletService {
|
||||
TransactionInput input = payoutTx.getInput(0);
|
||||
input.setScriptSig(inputScript);
|
||||
|
||||
BtcWalletService.printTx("disputed payoutTx", payoutTx);
|
||||
|
||||
verifyTransaction(payoutTx);
|
||||
checkWalletConsistency();
|
||||
checkScriptSig(payoutTx, input, 0);
|
||||
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
||||
input.verify(input.getConnectedOutput());
|
||||
|
||||
BtcWalletService.printTx("disputed payoutTx", payoutTx);
|
||||
|
||||
// As we use lockTime the tx will not be relayed as it is not considered standard.
|
||||
// We need to broadcast on our own when we reached the block height. Both peers will do the broadcast.
|
||||
return payoutTx;
|
||||
@ -848,8 +853,6 @@ public class TradeWalletService {
|
||||
String sellerPubKeyAsHex,
|
||||
String arbitratorPubKeyAsHex,
|
||||
String P2SHMultiSigOutputScript,
|
||||
List<String> buyerPubKeys,
|
||||
List<String> sellerPubKeys,
|
||||
FutureCallback<Transaction> callback)
|
||||
throws AddressFormatException, TransactionVerificationException, WalletException {
|
||||
log.info("signAndPublishPayoutTx called");
|
||||
@ -867,8 +870,6 @@ public class TradeWalletService {
|
||||
log.info("sellerPubKeyAsHex " + sellerPubKeyAsHex);
|
||||
log.info("arbitratorPubKeyAsHex " + arbitratorPubKeyAsHex);
|
||||
log.info("P2SHMultiSigOutputScript " + P2SHMultiSigOutputScript);
|
||||
log.info("buyerPubKeys " + buyerPubKeys);
|
||||
log.info("sellerPubKeys " + sellerPubKeys);
|
||||
|
||||
checkNotNull((buyerPrivateKeyAsHex != null || sellerPrivateKeyAsHex != null), "either buyerPrivateKeyAsHex or sellerPrivateKeyAsHex must not be null");
|
||||
|
||||
@ -878,34 +879,6 @@ public class TradeWalletService {
|
||||
|
||||
Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
|
||||
|
||||
if (!p2SHMultiSigOutputScript.toString().contains(P2SHMultiSigOutputScript)) {
|
||||
if (buyerPubKeys.isEmpty())
|
||||
buyerPubKeys.add(buyerPubKeyAsHex);
|
||||
if (sellerPubKeys.isEmpty())
|
||||
sellerPubKeys.add(sellerPubKeyAsHex);
|
||||
|
||||
boolean found = false;
|
||||
for (String b : buyerPubKeys) {
|
||||
if (found)
|
||||
break;
|
||||
byte[] bk = ECKey.fromPublicOnly(Utils.HEX.decode(b)).getPubKey();
|
||||
for (String s : sellerPubKeys) {
|
||||
byte[] sk = ECKey.fromPublicOnly(Utils.HEX.decode(s)).getPubKey();
|
||||
p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(bk, sk, arbitratorPubKey);
|
||||
if (p2SHMultiSigOutputScript.toString().contains(P2SHMultiSigOutputScript)) {
|
||||
log.info("Found buyers pub key " + b);
|
||||
log.info("Found sellers pub key " + s);
|
||||
buyerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(b)).getPubKey();
|
||||
sellerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(s)).getPubKey();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
log.warn("We did not find any matching pub keys for generating the required p2SHMultiSigOutputScript");
|
||||
}
|
||||
|
||||
Coin msOutput = buyerPayoutAmount.add(sellerPayoutAmount).add(arbitratorPayoutAmount).add(txFee);
|
||||
TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, null, msOutput, p2SHMultiSigOutputScript.getProgram());
|
||||
Transaction depositTx = new Transaction(params);
|
||||
@ -1185,7 +1158,7 @@ public class TradeWalletService {
|
||||
}
|
||||
}
|
||||
|
||||
private void addAvailableInputsAndChangeOutputs(Transaction transaction, AddressEntry addressEntry, Address changeAddress, Coin txFee) throws WalletException {
|
||||
private void addAvailableInputsAndChangeOutputs(Transaction transaction, Address address, Address changeAddress, Coin txFee) throws WalletException {
|
||||
try {
|
||||
// Lets let the framework do the work to find the right inputs
|
||||
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(transaction);
|
||||
@ -1195,7 +1168,7 @@ public class TradeWalletService {
|
||||
sendRequest.feePerKb = Coin.ZERO;
|
||||
sendRequest.fee = txFee;
|
||||
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation)
|
||||
sendRequest.coinSelector = new BtcCoinSelector(params, addressEntry.getAddress());
|
||||
sendRequest.coinSelector = new BtcCoinSelector(params, address);
|
||||
// We use always the same address in a trade for all transactions
|
||||
sendRequest.changeAddress = changeAddress;
|
||||
// With the usage of completeTx() we get all the work done with fee calculation, validation and coin selection.
|
||||
|
@ -445,7 +445,7 @@ public abstract class WalletService {
|
||||
sendRequest.feePerKb = getTxFeeForWithdrawalPerByte().multiply(1000);
|
||||
sendRequest.aesKey = aesKey;
|
||||
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
|
||||
printTx("emptyWallet", sendResult.tx);
|
||||
printTx("empty wallet", sendResult.tx);
|
||||
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(Transaction result) {
|
||||
@ -506,8 +506,8 @@ public abstract class WalletService {
|
||||
return wallet.freshKey(purpose);
|
||||
}
|
||||
|
||||
public DeterministicKey findKeyFromPubHash(byte[] pubkeyHash) {
|
||||
return wallet.getActiveKeychain().findKeyFromPubHash(pubkeyHash);
|
||||
public DeterministicKey findKeyFromPubKeyHash(byte[] pubKeyHash) {
|
||||
return wallet.getActiveKeychain().findKeyFromPubHash(pubKeyHash);
|
||||
}
|
||||
|
||||
public Address freshReceiveAddress() {
|
||||
|
@ -100,18 +100,20 @@ public class SquBlockchainManager {
|
||||
blockchainService.syncFromGenesisCompete(GENESIS_TX_ID,
|
||||
GENESIS_BLOCK_HEIGHT,
|
||||
block -> {
|
||||
UserThread.execute(() -> {
|
||||
try {
|
||||
blockchainService.parseBlock(new SquBlock(block.getTx(), block.getHeight()),
|
||||
GENESIS_BLOCK_HEIGHT,
|
||||
GENESIS_TX_ID,
|
||||
utxoByTxIdMap);
|
||||
} catch (SquBlockchainException e) {
|
||||
//TODO
|
||||
e.printStackTrace();
|
||||
}
|
||||
blockchainService.printUtxoMap(utxoByTxIdMap);
|
||||
});
|
||||
if (block != null) {
|
||||
UserThread.execute(() -> {
|
||||
try {
|
||||
blockchainService.parseBlock(new SquBlock(block.getTx(), block.getHeight()),
|
||||
GENESIS_BLOCK_HEIGHT,
|
||||
GENESIS_TX_ID,
|
||||
utxoByTxIdMap);
|
||||
} catch (SquBlockchainException e) {
|
||||
//TODO
|
||||
e.printStackTrace();
|
||||
}
|
||||
blockchainService.printUtxoMap(utxoByTxIdMap);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -73,18 +73,18 @@ abstract public class SquBlockchainService {
|
||||
String genesisTxId)
|
||||
throws SquBlockchainException {
|
||||
try {
|
||||
log.info("blockCount=" + chainHeadHeight);
|
||||
//log.info("blockCount=" + chainHeadHeight);
|
||||
long startTs = System.currentTimeMillis();
|
||||
for (int blockHeight = genesisBlockHeight; blockHeight <= chainHeadHeight; blockHeight++) {
|
||||
Block block = requestBlock(blockHeight);
|
||||
log.info("blockHeight=" + blockHeight);
|
||||
//log.info("blockHeight=" + blockHeight);
|
||||
parseBlock(new SquBlock(block.getTx(), block.getHeight()),
|
||||
genesisBlockHeight,
|
||||
genesisTxId,
|
||||
utxoByTxIdMap);
|
||||
}
|
||||
printUtxoMap(utxoByTxIdMap);
|
||||
log.info("Took {} ms", System.currentTimeMillis() - startTs);
|
||||
// log.info("Took {} ms", System.currentTimeMillis() - startTs);
|
||||
} catch (BitcoindException | CommunicationException e) {
|
||||
throw new SquBlockchainException(e.getMessage(), e);
|
||||
}
|
||||
@ -157,7 +157,7 @@ abstract public class SquBlockchainService {
|
||||
for (SquTransaction transaction : connectedTxs) {
|
||||
verifyTransaction(transaction, blockHeight, utxoByTxIdMap);
|
||||
}
|
||||
log.info("orphanTxs " + orphanTxs);
|
||||
//log.info("orphanTxs " + orphanTxs);
|
||||
if (!orphanTxs.isEmpty() && recursions < maxRecursions)
|
||||
resolveConnectedTxs(orphanTxs, utxoByTxIdMap, blockHeight, ++recursions, maxRecursions);
|
||||
}
|
||||
@ -286,6 +286,6 @@ abstract public class SquBlockchainService {
|
||||
.append(a.getValue().toString()).append("}\n");
|
||||
});
|
||||
});
|
||||
log.info(sb.toString());
|
||||
//log.info(sb.toString());
|
||||
}
|
||||
}
|
||||
|
@ -60,9 +60,9 @@ public final class Contract implements Payload {
|
||||
private final String offererPayoutAddressString;
|
||||
private final String takerPayoutAddressString;
|
||||
@JsonExclude
|
||||
private final byte[] offererBtcPubKey;
|
||||
private final byte[] offererMultiSigPubKey;
|
||||
@JsonExclude
|
||||
private final byte[] takerBtcPubKey;
|
||||
private final byte[] takerMultiSigPubKey;
|
||||
|
||||
public Contract(Offer offer,
|
||||
Coin tradeAmount,
|
||||
@ -80,8 +80,8 @@ public final class Contract implements Payload {
|
||||
PubKeyRing takerPubKeyRing,
|
||||
String offererPayoutAddressString,
|
||||
String takerPayoutAddressString,
|
||||
byte[] offererBtcPubKey,
|
||||
byte[] takerBtcPubKey) {
|
||||
byte[] offererMultiSigPubKey,
|
||||
byte[] takerMultiSigPubKey) {
|
||||
this.offer = offer;
|
||||
this.tradePrice = tradePrice.value;
|
||||
this.buyerNodeAddress = buyerNodeAddress;
|
||||
@ -98,8 +98,8 @@ public final class Contract implements Payload {
|
||||
this.takerPubKeyRing = takerPubKeyRing;
|
||||
this.offererPayoutAddressString = offererPayoutAddressString;
|
||||
this.takerPayoutAddressString = takerPayoutAddressString;
|
||||
this.offererBtcPubKey = offererBtcPubKey;
|
||||
this.takerBtcPubKey = takerBtcPubKey;
|
||||
this.offererMultiSigPubKey = offererMultiSigPubKey;
|
||||
this.takerMultiSigPubKey = takerMultiSigPubKey;
|
||||
}
|
||||
|
||||
public boolean isBuyerOffererAndSellerTaker() {
|
||||
@ -131,12 +131,12 @@ public final class Contract implements Payload {
|
||||
return isBuyerOffererAndSellerTaker ? takerPubKeyRing : offererPubKeyRing;
|
||||
}
|
||||
|
||||
public byte[] getBuyerBtcPubKey() {
|
||||
return isBuyerOffererAndSellerTaker ? offererBtcPubKey : takerBtcPubKey;
|
||||
public byte[] getBuyerMultiSigPubKey() {
|
||||
return isBuyerOffererAndSellerTaker ? offererMultiSigPubKey : takerMultiSigPubKey;
|
||||
}
|
||||
|
||||
public byte[] getSellerBtcPubKey() {
|
||||
return isBuyerOffererAndSellerTaker ? takerBtcPubKey : offererBtcPubKey;
|
||||
public byte[] getSellerMultiSigPubKey() {
|
||||
return isBuyerOffererAndSellerTaker ? takerMultiSigPubKey : offererMultiSigPubKey;
|
||||
}
|
||||
|
||||
public PaymentAccountContractData getBuyerPaymentAccountContractData() {
|
||||
@ -206,8 +206,8 @@ public final class Contract implements Payload {
|
||||
return false;
|
||||
if (takerPayoutAddressString != null ? !takerPayoutAddressString.equals(contract.takerPayoutAddressString) : contract.takerPayoutAddressString != null)
|
||||
return false;
|
||||
if (!Arrays.equals(offererBtcPubKey, contract.offererBtcPubKey)) return false;
|
||||
return Arrays.equals(takerBtcPubKey, contract.takerBtcPubKey);
|
||||
if (!Arrays.equals(offererMultiSigPubKey, contract.offererMultiSigPubKey)) return false;
|
||||
return Arrays.equals(takerMultiSigPubKey, contract.takerMultiSigPubKey);
|
||||
|
||||
}
|
||||
|
||||
@ -229,8 +229,8 @@ public final class Contract implements Payload {
|
||||
result = 31 * result + (sellerNodeAddress != null ? sellerNodeAddress.hashCode() : 0);
|
||||
result = 31 * result + (offererPayoutAddressString != null ? offererPayoutAddressString.hashCode() : 0);
|
||||
result = 31 * result + (takerPayoutAddressString != null ? takerPayoutAddressString.hashCode() : 0);
|
||||
result = 31 * result + (offererBtcPubKey != null ? Arrays.hashCode(offererBtcPubKey) : 0);
|
||||
result = 31 * result + (takerBtcPubKey != null ? Arrays.hashCode(takerBtcPubKey) : 0);
|
||||
result = 31 * result + (offererMultiSigPubKey != null ? Arrays.hashCode(offererMultiSigPubKey) : 0);
|
||||
result = 31 * result + (takerMultiSigPubKey != null ? Arrays.hashCode(takerMultiSigPubKey) : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -253,8 +253,8 @@ public final class Contract implements Payload {
|
||||
"\n\tsellerAddress=" + sellerNodeAddress +
|
||||
"\n\toffererPayoutAddressString='" + offererPayoutAddressString + '\'' +
|
||||
"\n\ttakerPayoutAddressString='" + takerPayoutAddressString + '\'' +
|
||||
"\n\toffererBtcPubKey=" + Arrays.toString(offererBtcPubKey) +
|
||||
"\n\ttakerBtcPubKey=" + Arrays.toString(takerBtcPubKey) +
|
||||
"\n\toffererMultiSigPubKey=" + Arrays.toString(offererMultiSigPubKey) +
|
||||
"\n\ttakerMultiSigPubKey=" + Arrays.toString(takerMultiSigPubKey) +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -389,7 +389,6 @@ public class TradeManager {
|
||||
|
||||
public void onWithdrawRequest(String toAddress, Coin amount, Coin fee, KeyParameter aesKey, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) {
|
||||
String fromAddress = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
|
||||
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
|
||||
|
@ -26,6 +26,7 @@ import io.bitsquare.p2p.NodeAddress;
|
||||
import io.bitsquare.trade.offer.Offer;
|
||||
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel;
|
||||
import io.bitsquare.trade.protocol.trade.ArbitrationSelectionRule;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -52,10 +53,13 @@ public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
|
||||
checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateOfferFeeTx");
|
||||
BtcWalletService walletService = model.walletService;
|
||||
String id = offer.getId();
|
||||
Address fundingAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress();
|
||||
Address reservedForTradeAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
Address changeAddress = walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
|
||||
Transaction transaction = model.tradeWalletService.createTradingFeeTx(
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress(),
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(),
|
||||
walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(),
|
||||
fundingAddress,
|
||||
reservedForTradeAddress,
|
||||
changeAddress,
|
||||
model.reservedFundsForOffer,
|
||||
model.useSavingsWallet,
|
||||
offer.getCreateOfferFee(),
|
||||
|
@ -121,7 +121,7 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
ProcessPublishDepositTxRequest.class,
|
||||
VerifyOffererAccount.class,
|
||||
VerifyAndSignContract.class,
|
||||
SignAndPublishDepositTxAsBuyer.class,
|
||||
TakerSignAndPublishDepositTxAsBuyer.class,
|
||||
SendDepositTxPublishedMessage.class,
|
||||
PublishTradeStatistics.class
|
||||
);
|
||||
|
@ -87,6 +87,7 @@ public class ProcessModel implements Model, Serializable {
|
||||
private Transaction takeOfferFeeTx;
|
||||
private boolean useSavingsWallet;
|
||||
private Coin fundsNeededForTrade;
|
||||
private byte[] myMultiSigPubKey;
|
||||
|
||||
public ProcessModel() {
|
||||
tradingPeer = new TradingPeer();
|
||||
@ -159,7 +160,7 @@ public class ProcessModel implements Model, Serializable {
|
||||
return user;
|
||||
}
|
||||
|
||||
public NodeAddress getMyAddress() {
|
||||
public NodeAddress getMyNodeAddress() {
|
||||
return p2PService.getAddress();
|
||||
}
|
||||
|
||||
@ -285,6 +286,14 @@ public class ProcessModel implements Model, Serializable {
|
||||
return takeOfferFeeTx;
|
||||
}
|
||||
|
||||
public void setMyMultiSigPubKey(byte[] myMultiSigPubKey) {
|
||||
this.myMultiSigPubKey = myMultiSigPubKey;
|
||||
}
|
||||
|
||||
public byte[] getMyMultiSigPubKey() {
|
||||
return myMultiSigPubKey;
|
||||
}
|
||||
|
||||
public boolean getUseSavingsWallet() {
|
||||
return useSavingsWallet;
|
||||
}
|
||||
|
@ -23,11 +23,17 @@ import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.crypto.Hash;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.protocol.trade.TradingPeer;
|
||||
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class OffererCreatesAndSignsDepositTxAsBuyer extends TradeTask {
|
||||
@ -56,21 +62,30 @@ public class OffererCreatesAndSignsDepositTxAsBuyer extends TradeTask {
|
||||
trade.setContractHash(contractHash);
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
AddressEntry buyerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
buyerMultiSigAddressEntry.setLockedTradeAmount(buyerInputAmount.subtract(trade.getTxFee()));
|
||||
|
||||
Optional<AddressEntry> addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present");
|
||||
AddressEntry buyerMultiSigAddressEntry = addressEntryOptional.get();
|
||||
buyerMultiSigAddressEntry.setCoinLockedInMultiSig(buyerInputAmount.subtract(trade.getTxFee()));
|
||||
walletService.saveAddressEntryList();
|
||||
Address offererAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
Address offererChangeAddress = walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
|
||||
TradingPeer tradingPeer = processModel.tradingPeer;
|
||||
byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
checkArgument(Arrays.equals(buyerMultiSigPubKey, buyerMultiSigAddressEntry.getPubKey()),
|
||||
"buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
PreparedDepositTxAndOffererInputs result = processModel.getTradeWalletService().offererCreatesAndSignsDepositTx(
|
||||
true,
|
||||
contractHash,
|
||||
buyerInputAmount,
|
||||
msOutputAmount,
|
||||
processModel.tradingPeer.getRawTransactionInputs(),
|
||||
processModel.tradingPeer.getChangeOutputValue(),
|
||||
processModel.tradingPeer.getChangeOutputAddress(),
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE),
|
||||
walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(),
|
||||
buyerMultiSigAddressEntry.getPubKey(),
|
||||
processModel.tradingPeer.getMultiSigPubKey(),
|
||||
tradingPeer.getRawTransactionInputs(),
|
||||
tradingPeer.getChangeOutputValue(),
|
||||
tradingPeer.getChangeOutputAddress(),
|
||||
offererAddress,
|
||||
offererChangeAddress,
|
||||
buyerMultiSigPubKey,
|
||||
tradingPeer.getMultiSigPubKey(),
|
||||
trade.getArbitratorPubKey());
|
||||
|
||||
processModel.setPreparedDepositTx(result.depositTransaction);
|
||||
|
@ -18,6 +18,7 @@
|
||||
package io.bitsquare.trade.protocol.trade.tasks.buyer;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.p2p.messaging.SendMailboxMessageListener;
|
||||
import io.bitsquare.trade.Trade;
|
||||
@ -38,13 +39,15 @@ public class SendFiatTransferStartedMessage extends TradeTask {
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
AddressEntry payoutAddressEntry = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT);
|
||||
processModel.getP2PService().sendEncryptedMailboxMessage(
|
||||
trade.getTradingPeerNodeAddress(),
|
||||
processModel.tradingPeer.getPubKeyRing(),
|
||||
new FiatTransferStartedMessage(
|
||||
processModel.getId(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
|
||||
processModel.getMyAddress()
|
||||
payoutAddressEntry.getAddressString(),
|
||||
processModel.getMyNodeAddress()
|
||||
),
|
||||
new SendMailboxMessageListener() {
|
||||
@Override
|
||||
|
@ -44,7 +44,7 @@ public class SendPayoutTxFinalizedMessage extends TradeTask {
|
||||
new PayoutTxFinalizedMessage(
|
||||
processModel.getId(),
|
||||
trade.getPayoutTx().bitcoinSerialize(),
|
||||
processModel.getMyAddress()
|
||||
processModel.getMyNodeAddress()
|
||||
),
|
||||
new SendMailboxMessageListener() {
|
||||
@Override
|
||||
|
@ -18,11 +18,14 @@
|
||||
package io.bitsquare.trade.protocol.trade.tasks.buyer;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.protocol.trade.TradingPeer;
|
||||
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -45,17 +48,23 @@ public class SignAndFinalizePayoutTx extends TradeTask {
|
||||
Coin sellerPayoutAmount = trade.getOffer().getSecurityDeposit();
|
||||
Coin buyerPayoutAmount = sellerPayoutAmount.add(trade.getTradeAmount());
|
||||
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
String buyerPayoutAddressString = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(id, buyerMultiSigPubKey);
|
||||
TradingPeer tradingPeer = processModel.tradingPeer;
|
||||
Transaction transaction = processModel.getTradeWalletService().buyerSignsAndFinalizesPayoutTx(
|
||||
trade.getDepositTx(),
|
||||
processModel.tradingPeer.getSignature(),
|
||||
tradingPeer.getSignature(),
|
||||
buyerPayoutAmount,
|
||||
sellerPayoutAmount,
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG),
|
||||
processModel.tradingPeer.getPayoutAddressString(),
|
||||
buyerPayoutAddressString,
|
||||
tradingPeer.getPayoutAddressString(),
|
||||
multiSigKeyPair,
|
||||
trade.getLockTimeAsBlockHeight(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(),
|
||||
processModel.tradingPeer.getMultiSigPubKey(),
|
||||
buyerMultiSigPubKey,
|
||||
tradingPeer.getMultiSigPubKey(),
|
||||
trade.getArbitratorPubKey()
|
||||
);
|
||||
|
||||
|
@ -1,94 +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.trade.protocol.trade.tasks.buyer;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.data.RawTransactionInput;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.crypto.Hash;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.protocol.trade.TradingPeer;
|
||||
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SignAndPublishDepositTxAsBuyer extends TradeTask {
|
||||
private static final Logger log = LoggerFactory.getLogger(SignAndPublishDepositTxAsBuyer.class);
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public SignAndPublishDepositTxAsBuyer(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
log.debug("\n\n------------------------------------------------------------\n"
|
||||
+ "Contract as json\n"
|
||||
+ trade.getContractAsJson()
|
||||
+ "\n------------------------------------------------------------\n");
|
||||
|
||||
|
||||
byte[] contractHash = Hash.getHash(trade.getContractAsJson());
|
||||
trade.setContractHash(contractHash);
|
||||
ArrayList<RawTransactionInput> buyerInputs = processModel.getRawTransactionInputs();
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
AddressEntry buyerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG);
|
||||
buyerMultiSigAddressEntry.setLockedTradeAmount(Coin.valueOf(buyerInputs.stream().mapToLong(input -> input.value).sum()).subtract(trade.getTxFee()));
|
||||
walletService.saveAddressEntryList();
|
||||
TradingPeer tradingPeer = processModel.tradingPeer;
|
||||
Transaction depositTx = processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(
|
||||
false,
|
||||
contractHash,
|
||||
processModel.getPreparedDepositTx(),
|
||||
buyerInputs,
|
||||
tradingPeer.getRawTransactionInputs(),
|
||||
buyerMultiSigAddressEntry.getPubKey(),
|
||||
tradingPeer.getMultiSigPubKey(),
|
||||
trade.getArbitratorPubKey(),
|
||||
new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(Transaction transaction) {
|
||||
log.trace("takerSignAndPublishTx succeeded " + transaction);
|
||||
|
||||
trade.setDepositTx(transaction);
|
||||
trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
});
|
||||
trade.setDepositTx(depositTx);
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,9 +19,11 @@ package io.bitsquare.trade.protocol.trade.tasks.buyer;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.data.InputsAndChangeOutput;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -42,11 +44,14 @@ public class TakerCreatesDepositTxInputsAsBuyer extends TradeTask {
|
||||
Coin txFee = trade.getTxFee();
|
||||
Coin doubleTxFee = txFee.add(txFee);
|
||||
Coin takerInputAmount = trade.getOffer().getSecurityDeposit().add(doubleTxFee);
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
Address takersAddress = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
Address takersChangeAddress = walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
|
||||
InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(
|
||||
takerInputAmount,
|
||||
txFee,
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.RESERVED_FOR_TRADE),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress());
|
||||
takersAddress,
|
||||
takersChangeAddress);
|
||||
processModel.setRawTransactionInputs(result.rawTransactionInputs);
|
||||
processModel.setChangeOutputValue(result.changeOutputValue);
|
||||
processModel.setChangeOutputAddress(result.changeOutputAddress);
|
||||
|
@ -18,6 +18,7 @@
|
||||
package io.bitsquare.trade.protocol.trade.tasks.offerer;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.crypto.Sig;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.common.util.Utilities;
|
||||
@ -31,6 +32,7 @@ import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class CreateAndSignContract extends TradeTask {
|
||||
@ -53,11 +55,14 @@ public class CreateAndSignContract extends TradeTask {
|
||||
PaymentAccountContractData takerPaymentAccountContractData = taker.getPaymentAccountContractData();
|
||||
boolean isBuyerOffererAndSellerTaker = trade instanceof BuyerAsOffererTrade;
|
||||
|
||||
NodeAddress buyerNodeAddress = isBuyerOffererAndSellerTaker ? processModel.getMyAddress() : processModel.getTempTradingPeerNodeAddress();
|
||||
NodeAddress sellerNodeAddress = isBuyerOffererAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyAddress();
|
||||
log.debug("isBuyerOffererAndSellerTaker " + isBuyerOffererAndSellerTaker);
|
||||
log.debug("buyerAddress " + buyerNodeAddress);
|
||||
log.debug("sellerAddress " + sellerNodeAddress);
|
||||
NodeAddress buyerNodeAddress = isBuyerOffererAndSellerTaker ? processModel.getMyNodeAddress() : processModel.getTempTradingPeerNodeAddress();
|
||||
NodeAddress sellerNodeAddress = isBuyerOffererAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyNodeAddress();
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
AddressEntry takerAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT);
|
||||
checkArgument(!walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG).isPresent(), "addressEntry must not be set here.");
|
||||
AddressEntry offererAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
byte[] offererMultiSigPubKey = offererAddressEntry.getPubKey();
|
||||
Contract contract = new Contract(
|
||||
processModel.getOffer(),
|
||||
trade.getTradeAmount(),
|
||||
@ -73,9 +78,9 @@ public class CreateAndSignContract extends TradeTask {
|
||||
takerPaymentAccountContractData,
|
||||
processModel.getPubKeyRing(),
|
||||
taker.getPubKeyRing(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
|
||||
takerAddressEntry.getAddressString(),
|
||||
taker.getPayoutAddressString(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(),
|
||||
offererMultiSigPubKey,
|
||||
taker.getMultiSigPubKey()
|
||||
);
|
||||
String contractAsJson = Utilities.objectToJson(contract);
|
||||
@ -84,6 +89,7 @@ public class CreateAndSignContract extends TradeTask {
|
||||
trade.setContract(contract);
|
||||
trade.setContractAsJson(contractAsJson);
|
||||
trade.setOffererContractSignature(signature);
|
||||
processModel.setMyMultiSigPubKey(offererMultiSigPubKey);
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package io.bitsquare.trade.protocol.trade.tasks.offerer;
|
||||
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.trade.OffererTrade;
|
||||
import io.bitsquare.trade.Trade;
|
||||
@ -51,8 +52,10 @@ public class ProcessDepositTxPublishedMessage extends TradeTask {
|
||||
// To access tx confidence we need to add that tx into our wallet.
|
||||
Transaction transactionFromSerializedTx = processModel.getWalletService().getTransactionFromSerializedTx(message.depositTx);
|
||||
// update with full tx
|
||||
trade.setDepositTx(processModel.getTradeWalletService().addTransactionToWallet(transactionFromSerializedTx));
|
||||
|
||||
Transaction walletTx = processModel.getTradeWalletService().addTransactionToWallet(transactionFromSerializedTx);
|
||||
trade.setDepositTx(walletTx);
|
||||
BtcWalletService.printTx("depositTx received from peer", walletTx);
|
||||
|
||||
if (trade instanceof OffererTrade)
|
||||
processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer());
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
package io.bitsquare.trade.protocol.trade.tasks.offerer;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.p2p.messaging.SendDirectMessageListener;
|
||||
import io.bitsquare.trade.Trade;
|
||||
@ -26,6 +27,11 @@ import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
public class SendPublishDepositTxRequest extends TradeTask {
|
||||
private static final Logger log = LoggerFactory.getLogger(SendPublishDepositTxRequest.class);
|
||||
|
||||
@ -38,14 +44,25 @@ public class SendPublishDepositTxRequest extends TradeTask {
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
|
||||
Optional<AddressEntry> addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
checkArgument(addressEntryOptional.isPresent(), "addressEntry must be set here.");
|
||||
AddressEntry offererPayoutAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT);
|
||||
byte[] offererMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
checkArgument(Arrays.equals(offererMultiSigPubKey,
|
||||
addressEntryOptional.get().getPubKey()),
|
||||
"offererMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
|
||||
PublishDepositTxRequest tradeMessage = new PublishDepositTxRequest(
|
||||
processModel.getId(),
|
||||
processModel.getPaymentAccountContractData(trade),
|
||||
processModel.getAccountId(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(),
|
||||
offererMultiSigPubKey,
|
||||
trade.getContractAsJson(),
|
||||
trade.getOffererContractSignature(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
|
||||
offererPayoutAddressEntry.getAddressString(),
|
||||
processModel.getPreparedDepositTx(),
|
||||
processModel.getRawTransactionInputs()
|
||||
);
|
||||
|
@ -23,11 +23,17 @@ import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.crypto.Hash;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.protocol.trade.TradingPeer;
|
||||
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class OffererCreatesAndSignsDepositTxAsSeller extends TradeTask {
|
||||
@ -56,21 +62,31 @@ public class OffererCreatesAndSignsDepositTxAsSeller extends TradeTask {
|
||||
trade.setContractHash(contractHash);
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
AddressEntry sellerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
sellerMultiSigAddressEntry.setLockedTradeAmount(sellerInputAmount.subtract(trade.getTxFee()));
|
||||
walletService.saveAddressEntryList();
|
||||
|
||||
Optional<AddressEntry> addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
checkArgument(addressEntryOptional.isPresent(), "addressEntry must be set here.");
|
||||
AddressEntry sellerMultiSigAddressEntry = addressEntryOptional.get();
|
||||
sellerMultiSigAddressEntry.setCoinLockedInMultiSig(sellerInputAmount.subtract(trade.getTxFee()));
|
||||
byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
checkArgument(Arrays.equals(sellerMultiSigPubKey,
|
||||
sellerMultiSigAddressEntry.getPubKey()),
|
||||
"sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
|
||||
Address offererAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
Address offererChangeAddress = walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
|
||||
TradingPeer tradingPeer = processModel.tradingPeer;
|
||||
PreparedDepositTxAndOffererInputs result = processModel.getTradeWalletService().offererCreatesAndSignsDepositTx(
|
||||
false,
|
||||
contractHash,
|
||||
sellerInputAmount,
|
||||
msOutputAmount,
|
||||
processModel.tradingPeer.getRawTransactionInputs(),
|
||||
processModel.tradingPeer.getChangeOutputValue(),
|
||||
processModel.tradingPeer.getChangeOutputAddress(),
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE),
|
||||
walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(),
|
||||
processModel.tradingPeer.getMultiSigPubKey(),
|
||||
sellerMultiSigAddressEntry.getPubKey(),
|
||||
tradingPeer.getRawTransactionInputs(),
|
||||
tradingPeer.getChangeOutputValue(),
|
||||
tradingPeer.getChangeOutputAddress(),
|
||||
offererAddress,
|
||||
offererChangeAddress,
|
||||
tradingPeer.getMultiSigPubKey(),
|
||||
sellerMultiSigPubKey,
|
||||
trade.getArbitratorPubKey());
|
||||
|
||||
processModel.setPreparedDepositTx(result.depositTransaction);
|
||||
|
@ -17,10 +17,12 @@
|
||||
|
||||
package io.bitsquare.trade.protocol.trade.tasks.seller;
|
||||
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.protocol.trade.messages.PayoutTxFinalizedMessage;
|
||||
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -45,7 +47,9 @@ public class ProcessPayoutTxFinalizedMessage extends TradeTask {
|
||||
checkTradeId(processModel.getId(), message);
|
||||
checkNotNull(message);
|
||||
checkArgument(message.payoutTx != null);
|
||||
trade.setPayoutTx(processModel.getTradeWalletService().addTransactionToWallet(message.payoutTx));
|
||||
Transaction walletTx = processModel.getTradeWalletService().addTransactionToWallet(message.payoutTx);
|
||||
trade.setPayoutTx(walletTx);
|
||||
BtcWalletService.printTx("payoutTx received from peer", walletTx);
|
||||
|
||||
|
||||
// update to the latest peer address of our peer if the message is correct
|
||||
|
@ -18,6 +18,7 @@
|
||||
package io.bitsquare.trade.protocol.trade.tasks.seller;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.p2p.messaging.SendMailboxMessageListener;
|
||||
import io.bitsquare.trade.Trade;
|
||||
@ -39,12 +40,14 @@ public class SendFinalizePayoutTxRequest extends TradeTask {
|
||||
try {
|
||||
runInterceptHook();
|
||||
if (trade.getTradingPeerNodeAddress() != null) {
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
String sellerPayoutAddress = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
FinalizePayoutTxRequest message = new FinalizePayoutTxRequest(
|
||||
processModel.getId(),
|
||||
processModel.getPayoutTxSignature(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
|
||||
sellerPayoutAddress,
|
||||
trade.getLockTimeAsBlockHeight(),
|
||||
processModel.getMyAddress()
|
||||
processModel.getMyNodeAddress()
|
||||
);
|
||||
|
||||
processModel.getP2PService().sendEncryptedMailboxMessage(
|
||||
|
@ -33,6 +33,10 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
public class SignAndPublishDepositTxAsSeller extends TradeTask {
|
||||
private static final Logger log = LoggerFactory.getLogger(SignAndPublishDepositTxAsSeller.class);
|
||||
@ -56,9 +60,18 @@ public class SignAndPublishDepositTxAsSeller extends TradeTask {
|
||||
|
||||
ArrayList<RawTransactionInput> sellerInputs = processModel.getRawTransactionInputs();
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
AddressEntry sellerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG);
|
||||
sellerMultiSigAddressEntry.setLockedTradeAmount(Coin.valueOf(sellerInputs.stream().mapToLong(input -> input.value).sum()).subtract(trade.getTxFee()));
|
||||
walletService.saveAddressEntryList();
|
||||
String id = processModel.getOffer().getId();
|
||||
|
||||
Optional<AddressEntry> addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present");
|
||||
AddressEntry sellerMultiSigAddressEntry = addressEntryOptional.get();
|
||||
byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
checkArgument(Arrays.equals(sellerMultiSigPubKey,
|
||||
sellerMultiSigAddressEntry.getPubKey()),
|
||||
"sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
|
||||
Coin sellerInput = Coin.valueOf(sellerInputs.stream().mapToLong(input -> input.value).sum());
|
||||
sellerMultiSigAddressEntry.setCoinLockedInMultiSig(sellerInput.subtract(trade.getTxFee()));
|
||||
TradingPeer tradingPeer = processModel.tradingPeer;
|
||||
Transaction depositTx = processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(
|
||||
true,
|
||||
@ -67,7 +80,7 @@ public class SignAndPublishDepositTxAsSeller extends TradeTask {
|
||||
tradingPeer.getRawTransactionInputs(),
|
||||
sellerInputs,
|
||||
tradingPeer.getMultiSigPubKey(),
|
||||
sellerMultiSigAddressEntry.getPubKey(),
|
||||
sellerMultiSigPubKey,
|
||||
trade.getArbitratorPubKey(),
|
||||
new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
|
@ -23,9 +23,13 @@ import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class SignPayoutTx extends TradeTask {
|
||||
@ -58,16 +62,23 @@ public class SignPayoutTx extends TradeTask {
|
||||
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
String sellerPayoutAddressString = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(id, processModel.getMyMultiSigPubKey());
|
||||
byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
checkArgument(Arrays.equals(sellerMultiSigPubKey,
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
|
||||
"sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
|
||||
byte[] payoutTxSignature = processModel.getTradeWalletService().sellerSignsPayoutTx(
|
||||
trade.getDepositTx(),
|
||||
buyerPayoutAmount,
|
||||
sellerPayoutAmount,
|
||||
processModel.tradingPeer.getPayoutAddressString(),
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT),
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG),
|
||||
sellerPayoutAddressString,
|
||||
multiSigKeyPair,
|
||||
lockTimeAsBlockHeight,
|
||||
processModel.tradingPeer.getMultiSigPubKey(),
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey(),
|
||||
sellerMultiSigPubKey,
|
||||
trade.getArbitratorPubKey());
|
||||
|
||||
processModel.setPayoutTxSignature(payoutTxSignature);
|
||||
|
@ -19,9 +19,11 @@ package io.bitsquare.trade.protocol.trade.tasks.seller;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.data.InputsAndChangeOutput;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -44,10 +46,15 @@ public class TakerCreatesDepositTxInputsAsSeller extends TradeTask {
|
||||
Coin doubleTxFee = txFee.add(txFee);
|
||||
Coin takerInputAmount = trade.getOffer().getSecurityDeposit().add(doubleTxFee).add(trade.getTradeAmount());
|
||||
|
||||
InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount,
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
Address takersAddress = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
Address takersChangeAddress = walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
|
||||
InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(
|
||||
takerInputAmount,
|
||||
txFee,
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.RESERVED_FOR_TRADE),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress());
|
||||
takersAddress,
|
||||
takersChangeAddress);
|
||||
|
||||
processModel.setRawTransactionInputs(result.rawTransactionInputs);
|
||||
processModel.setChangeOutputValue(result.changeOutputValue);
|
||||
processModel.setChangeOutputAddress(result.changeOutputAddress);
|
||||
|
@ -26,6 +26,7 @@ import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.protocol.trade.ArbitrationSelectionRule;
|
||||
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import io.bitsquare.user.User;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -52,10 +53,16 @@ public class CreateTakeOfferFeeTx extends TradeTask {
|
||||
checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateTakeOfferFeeTx");
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
String id = model.getOffer().getId();
|
||||
AddressEntry addressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING);
|
||||
AddressEntry reservedForTradeAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE);
|
||||
AddressEntry changeAddressEntry = walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE);
|
||||
Address fundingAddress = addressEntry.getAddress();
|
||||
Address reservedForTradeAddress = reservedForTradeAddressEntry.getAddress();
|
||||
Address changeAddress = changeAddressEntry.getAddress();
|
||||
Transaction createTakeOfferFeeTx = processModel.getTradeWalletService().createTradingFeeTx(
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress(),
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(),
|
||||
walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(),
|
||||
fundingAddress,
|
||||
reservedForTradeAddress,
|
||||
changeAddress,
|
||||
processModel.getFundsNeededForTrade(),
|
||||
processModel.getUseSavingsWallet(),
|
||||
trade.getTakeOfferFee(),
|
||||
|
@ -40,7 +40,7 @@ public class SendDepositTxPublishedMessage extends TradeTask {
|
||||
if (trade.getDepositTx() != null) {
|
||||
DepositTxPublishedMessage tradeMessage = new DepositTxPublishedMessage(processModel.getId(),
|
||||
trade.getDepositTx().bitcoinSerialize(),
|
||||
processModel.getMyAddress());
|
||||
processModel.getMyNodeAddress());
|
||||
|
||||
processModel.getP2PService().sendEncryptedMailboxMessage(
|
||||
trade.getTradingPeerNodeAddress(),
|
||||
|
@ -18,6 +18,7 @@
|
||||
package io.bitsquare.trade.protocol.trade.tasks.taker;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.p2p.messaging.SendMailboxMessageListener;
|
||||
import io.bitsquare.trade.Trade;
|
||||
@ -28,6 +29,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class SendPayDepositRequest extends TradeTask {
|
||||
@ -46,8 +48,15 @@ public class SendPayDepositRequest extends TradeTask {
|
||||
checkNotNull(trade.getTradeAmount(), "TradeAmount must not be null");
|
||||
checkNotNull(trade.getTakeOfferFeeTxId(), "TakeOfferFeeTxId must not be null");
|
||||
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
AddressEntry takerPayoutAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT);
|
||||
checkArgument(!walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG).isPresent(), "addressEntry must not be set here.");
|
||||
AddressEntry addressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
byte[] takerMultiSigPubKey = addressEntry.getPubKey();
|
||||
String takerPayoutAddressString = takerPayoutAddressEntry.getAddressString();
|
||||
PayDepositRequest payDepositRequest = new PayDepositRequest(
|
||||
processModel.getMyAddress(),
|
||||
processModel.getMyNodeAddress(),
|
||||
processModel.getId(),
|
||||
trade.getTradeAmount().value,
|
||||
trade.getTradePrice().value,
|
||||
@ -56,8 +65,8 @@ public class SendPayDepositRequest extends TradeTask {
|
||||
processModel.getRawTransactionInputs(),
|
||||
processModel.getChangeOutputValue(),
|
||||
processModel.getChangeOutputAddress(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
|
||||
takerMultiSigPubKey,
|
||||
takerPayoutAddressString,
|
||||
processModel.getPubKeyRing(),
|
||||
processModel.getPaymentAccountContractData(trade),
|
||||
processModel.getAccountId(),
|
||||
@ -65,6 +74,7 @@ public class SendPayDepositRequest extends TradeTask {
|
||||
new ArrayList<>(processModel.getUser().getAcceptedArbitratorAddresses()),
|
||||
trade.getArbitratorNodeAddress()
|
||||
);
|
||||
processModel.setMyMultiSigPubKey(takerMultiSigPubKey);
|
||||
|
||||
processModel.getP2PService().sendEncryptedMailboxMessage(
|
||||
trade.getTradingPeerNodeAddress(),
|
||||
|
@ -18,6 +18,7 @@
|
||||
package io.bitsquare.trade.protocol.trade.tasks.taker;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.common.crypto.Sig;
|
||||
import io.bitsquare.common.taskrunner.TaskRunner;
|
||||
import io.bitsquare.common.util.Utilities;
|
||||
@ -31,6 +32,10 @@ import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class VerifyAndSignContract extends TradeTask {
|
||||
@ -53,12 +58,24 @@ public class VerifyAndSignContract extends TradeTask {
|
||||
PaymentAccountContractData takerPaymentAccountContractData = processModel.getPaymentAccountContractData(trade);
|
||||
|
||||
boolean isBuyerOffererAndSellerTaker = trade instanceof SellerAsTakerTrade;
|
||||
NodeAddress buyerNodeAddress = isBuyerOffererAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyAddress();
|
||||
NodeAddress sellerNodeAddress = isBuyerOffererAndSellerTaker ? processModel.getMyAddress() : processModel.getTempTradingPeerNodeAddress();
|
||||
NodeAddress buyerNodeAddress = isBuyerOffererAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyNodeAddress();
|
||||
NodeAddress sellerNodeAddress = isBuyerOffererAndSellerTaker ? processModel.getMyNodeAddress() : processModel.getTempTradingPeerNodeAddress();
|
||||
log.debug("isBuyerOffererAndSellerTaker " + isBuyerOffererAndSellerTaker);
|
||||
log.debug("buyerAddress " + buyerNodeAddress);
|
||||
log.debug("sellerAddress " + sellerNodeAddress);
|
||||
|
||||
BtcWalletService walletService = processModel.getWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
AddressEntry takerPayoutAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT);
|
||||
String takerPayoutAddressString = takerPayoutAddressEntry.getAddressString();
|
||||
Optional<AddressEntry> addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present");
|
||||
AddressEntry takerMultiSigAddressEntry = addressEntryOptional.get();
|
||||
byte[] takerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
checkArgument(Arrays.equals(takerMultiSigPubKey,
|
||||
takerMultiSigAddressEntry.getPubKey()),
|
||||
"takerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
|
||||
Contract contract = new Contract(
|
||||
processModel.getOffer(),
|
||||
trade.getTradeAmount(),
|
||||
@ -75,9 +92,9 @@ public class VerifyAndSignContract extends TradeTask {
|
||||
offerer.getPubKeyRing(),
|
||||
processModel.getPubKeyRing(),
|
||||
offerer.getPayoutAddressString(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
|
||||
takerPayoutAddressString,
|
||||
offerer.getMultiSigPubKey(),
|
||||
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey()
|
||||
takerMultiSigPubKey
|
||||
);
|
||||
String contractAsJson = Utilities.objectToJson(contract);
|
||||
String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson);
|
||||
|
@ -982,7 +982,7 @@ public class MainViewModel implements ViewModel {
|
||||
private void updateLockedBalance() {
|
||||
Coin sum = Coin.valueOf(tradeManager.getLockedTradeStream()
|
||||
.mapToLong(trade -> {
|
||||
Coin lockedTradeAmount = btcWalletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG).getLockedTradeAmount();
|
||||
Coin lockedTradeAmount = btcWalletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG).getCoinLockedInMultiSig();
|
||||
return lockedTradeAmount != null ? lockedTradeAmount.getValue() : 0;
|
||||
})
|
||||
.sum());
|
||||
|
@ -134,7 +134,7 @@ public class CreateCompensationRequestView extends ActivatableView<GridPane, Voi
|
||||
// We use the key of the first SQU input for signing the data
|
||||
TransactionOutput connectedOutput = preparedSendTx.getInputs().get(0).getConnectedOutput();
|
||||
checkNotNull(connectedOutput, "connectedOutput must not be null");
|
||||
DeterministicKey squKeyPair = squWalletService.findKeyFromPubHash(connectedOutput.getScriptPubKey().getPubKeyHash());
|
||||
DeterministicKey squKeyPair = squWalletService.findKeyFromPubKeyHash(connectedOutput.getScriptPubKey().getPubKeyHash());
|
||||
checkNotNull(squKeyPair, "squKeyPair must not be null");
|
||||
|
||||
// We get the JSON of the object excluding signature and feeTxId
|
||||
|
@ -71,7 +71,7 @@ class LockedListItem {
|
||||
}
|
||||
|
||||
private void updateBalance() {
|
||||
balance = addressEntry.getLockedTradeAmount();
|
||||
balance = addressEntry.getCoinLockedInMultiSig();
|
||||
if (balance != null)
|
||||
balanceLabel.setText(formatter.formatCoin(this.balance));
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import io.bitsquare.gui.common.model.ActivatableViewModel;
|
||||
import io.bitsquare.gui.main.offer.offerbook.OfferBook;
|
||||
import io.bitsquare.gui.main.offer.offerbook.OfferBookListItem;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import io.bitsquare.locale.CurrencyUtil;
|
||||
import io.bitsquare.trade.offer.Offer;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
@ -110,17 +111,23 @@ class SpreadViewModel extends ActivatableViewModel {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Fiat spread = null;
|
||||
String percentage = "";
|
||||
Fiat bestSellOfferPrice = sellOffers.isEmpty() ? null : sellOffers.get(0).getPrice();
|
||||
Fiat bestBuyOfferPrice = buyOffers.isEmpty() ? null : buyOffers.get(0).getPrice();
|
||||
if (bestBuyOfferPrice != null && bestSellOfferPrice != null)
|
||||
if (bestBuyOfferPrice != null && bestSellOfferPrice != null) {
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
spread = bestSellOfferPrice.subtract(bestBuyOfferPrice);
|
||||
|
||||
String percentage = "";
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
if (spread != null && marketPrice != null) {
|
||||
double marketPriceAsDouble = marketPrice.getPrice(PriceFeedService.Type.LAST);
|
||||
double result = ((double) spread.value / 10000) / marketPriceAsDouble;
|
||||
percentage = " (" + formatter.formatPercentagePrice(result) + ")";
|
||||
if (spread != null && marketPrice != null) {
|
||||
double marketPriceAsDouble = marketPrice.getPrice(PriceFeedService.Type.LAST);
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
double result = ((double) spread.value / 10000D) / marketPriceAsDouble;
|
||||
percentage = " (" + formatter.formatPercentagePrice(result) + ")";
|
||||
} else {
|
||||
final double spreadAsDouble = spread.value != 0 ? 10000D / spread.value : 0;
|
||||
double result = marketPriceAsDouble / spreadAsDouble;
|
||||
percentage = " (" + formatter.formatPercentagePrice(result) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Coin totalAmount = Coin.valueOf(offers.stream().mapToLong(offer -> offer.getAmount().getValue()).sum());
|
||||
|
@ -22,6 +22,7 @@ import io.bitsquare.app.DevFlags;
|
||||
import io.bitsquare.app.Version;
|
||||
import io.bitsquare.arbitration.Arbitrator;
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
@ -143,8 +144,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
|
||||
useMarketBasedPrice.set(preferences.getUsePercentageBasedPrice());
|
||||
|
||||
// TODO add ui for editing
|
||||
securityDepositAsCoin = Coin.valueOf(1_000_000);
|
||||
// TODO add ui for editing, use preferences
|
||||
securityDepositAsCoin = FeePolicy.getDefaultSecurityDeposit();
|
||||
|
||||
balanceListener = new BalanceListener(getAddressEntry().getAddress()) {
|
||||
@Override
|
||||
@ -255,15 +256,14 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
// not too many inputs.
|
||||
|
||||
// trade fee tx: 226 bytes (1 input) - 374 bytes (2 inputs)
|
||||
feeService.requestFees(() -> {
|
||||
createOfferFeeAsCoin = feeService.getCreateOfferFee();
|
||||
txFeeAsCoin = feeService.getTxFee(400);
|
||||
calculateTotalToPay();
|
||||
}, null);
|
||||
|
||||
createOfferFeeAsCoin = feeService.getCreateOfferFee();
|
||||
// Set the default values (in rare cases if the fee request was not done yet we get the hard coded default values)
|
||||
// But offer creation happens usually after that so we should have already the value from the estimation service.
|
||||
txFeeAsCoin = feeService.getTxFee(400);
|
||||
|
||||
// We request to get the actual estimated fee
|
||||
requestTxFee();
|
||||
|
||||
calculateVolume();
|
||||
calculateTotalToPay();
|
||||
return true;
|
||||
@ -313,6 +313,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
String countryCode = paymentAccount instanceof CountryBasedPaymentAccount ? ((CountryBasedPaymentAccount) paymentAccount).getCountry().code : null;
|
||||
|
||||
checkNotNull(p2PService.getAddress(), "Address must not be null");
|
||||
checkNotNull(createOfferFeeAsCoin, "createOfferFeeAsCoin must not be null");
|
||||
|
||||
long maxTradeLimit = paymentAccount.getPaymentMethod().getMaxTradeLimit().value;
|
||||
long maxTradePeriod = paymentAccount.getPaymentMethod().getMaxTradePeriod();
|
||||
@ -362,6 +363,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
}
|
||||
|
||||
void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) {
|
||||
checkNotNull(createOfferFeeAsCoin, "createOfferFeeAsCoin must not be null");
|
||||
openOfferManager.placeOffer(offer, totalToPayAsCoin.get().subtract(txFeeAsCoin).subtract(createOfferFeeAsCoin), useSavingsWallet, resultHandler);
|
||||
}
|
||||
|
||||
@ -419,6 +421,12 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
this.marketPriceMargin = marketPriceMargin;
|
||||
}
|
||||
|
||||
void requestTxFee() {
|
||||
feeService.requestFees(() -> {
|
||||
txFeeAsCoin = feeService.getTxFee(400);
|
||||
calculateTotalToPay();
|
||||
}, null);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
@ -511,7 +519,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
// Offerer does not pay the tx fee for the trade txs 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 createOfferFee tx is deducted from the createOfferFee and not visible to the trader
|
||||
if (direction != null && amount.get() != null) {
|
||||
if (direction != null && amount.get() != null && createOfferFeeAsCoin != null) {
|
||||
Coin feeAndSecDeposit = createOfferFeeAsCoin.add(txFeeAsCoin).add(securityDepositAsCoin);
|
||||
Coin required = direction == Offer.Direction.BUY ? feeAndSecDeposit : feeAndSecDeposit.add(amount.get());
|
||||
totalToPayAsCoin.set(required);
|
||||
@ -560,6 +568,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
}
|
||||
|
||||
public Coin getCreateOfferFeeAsCoin() {
|
||||
checkNotNull(createOfferFeeAsCoin, "createOfferFeeAsCoin must not be null");
|
||||
return createOfferFeeAsCoin;
|
||||
}
|
||||
|
||||
@ -596,4 +605,17 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void setAmount(Coin amount) {
|
||||
this.amount.set(amount);
|
||||
}
|
||||
|
||||
void updateTradeFee() {
|
||||
createOfferFeeAsCoin = Utilities.getFeePerBtc(feeService.getCreateOfferFeeInBtcPerBtc(), amount.get());
|
||||
|
||||
// We don't want too fractional btc values so we use only a divide by 10 instead of 100
|
||||
createOfferFeeAsCoin = createOfferFeeAsCoin.divide(10).multiply(Math.round(marketPriceMargin * 1_000));
|
||||
|
||||
createOfferFeeAsCoin = Utilities.maxCoin(createOfferFeeAsCoin, feeService.getMinCreateOfferFeeInBtc());
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
imageView.setId("image-buy-large");
|
||||
|
||||
placeOfferButton.setId("buy-button-big");
|
||||
placeOfferButton.setText("Review offer to buy bitcoin");
|
||||
placeOfferButton.setText("Review: Place offer to buy bitcoin");
|
||||
nextButton.setId("buy-button");
|
||||
} else {
|
||||
imageView.setId("image-sell-large");
|
||||
@ -256,7 +256,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
totalToPayTextField.setPromptText(BSResources.get("createOffer.fundsBox.totalsNeeded.prompt"));
|
||||
|
||||
placeOfferButton.setId("sell-button-big");
|
||||
placeOfferButton.setText("Review offer to sell bitcoin");
|
||||
placeOfferButton.setText("Review: Place offer to sell bitcoin");
|
||||
nextButton.setId("sell-button");
|
||||
}
|
||||
}
|
||||
@ -334,7 +334,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
.show();
|
||||
|
||||
key = "createOfferFundWalletInfo";
|
||||
String tradeAmountText = model.isSellOffer() ? "- Trade amount: " + model.tradeAmount.get() + "\n" : "";
|
||||
String tradeAmountText = model.isSellOffer() ? "- Trade amount: " + model.getTradeAmount() + "\n" : "";
|
||||
|
||||
new Popup().headLine("Fund your offer").instruction("You need to deposit " +
|
||||
model.totalToPay.get() + " to this offer.\n\n" +
|
||||
@ -345,7 +345,7 @@ 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" +
|
||||
"- Trading fee: " + model.getCreateOfferFee() + "\n" +
|
||||
"- Mining fee: " + model.getTxFee() + "\n\n" +
|
||||
|
||||
"You can choose between two options when funding your trade:\n" +
|
||||
@ -1078,7 +1078,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.tradeAmount"), model.tradeAmount.get());
|
||||
|
||||
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.offerFee"), model.getCreateOfferFee());
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.networkFee"), model.getTxFee());
|
||||
Separator separator = new Separator();
|
||||
separator.setOrientation(Orientation.HORIZONTAL);
|
||||
@ -1099,6 +1099,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
private void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) {
|
||||
Label label = new Label(labelText);
|
||||
TextField textField = new TextField(value);
|
||||
textField.setMinWidth(300);
|
||||
textField.setEditable(false);
|
||||
textField.setFocusTraversable(false);
|
||||
textField.setId("payment-info");
|
||||
|
@ -36,6 +36,7 @@ import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||
import io.bitsquare.gui.main.settings.SettingsView;
|
||||
import io.bitsquare.gui.main.settings.preferences.PreferencesView;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import io.bitsquare.gui.util.GUIUtil;
|
||||
import io.bitsquare.gui.util.validation.BtcValidator;
|
||||
import io.bitsquare.gui.util.validation.FiatValidator;
|
||||
import io.bitsquare.gui.util.validation.InputValidator;
|
||||
@ -265,7 +266,10 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
percentage = dataModel.getDirection() == Offer.Direction.SELL ? 1 - relation : relation - 1;
|
||||
else
|
||||
percentage = dataModel.getDirection() == Offer.Direction.BUY ? 1 - relation : relation - 1;
|
||||
|
||||
percentage = MathUtils.roundDouble(percentage, 4);
|
||||
dataModel.setMarketPriceMargin(percentage);
|
||||
dataModel.updateTradeFee();
|
||||
marketPriceMargin.set(formatter.formatToPercent(percentage));
|
||||
} catch (NumberFormatException t) {
|
||||
marketPriceMargin.set("");
|
||||
@ -293,7 +297,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
if (marketPrice != null) {
|
||||
percentage = MathUtils.roundDouble(percentage, 4);
|
||||
dataModel.setMarketPriceMargin(percentage);
|
||||
|
||||
dataModel.updateTradeFee();
|
||||
|
||||
double marketPriceAsDouble = marketPrice.getPrice(getPriceFeedType());
|
||||
double factor;
|
||||
if (CurrencyUtil.isCryptoCurrency(currencyCode))
|
||||
@ -495,6 +500,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
}
|
||||
|
||||
void onShowPayFundsScreen() {
|
||||
dataModel.requestTxFee();
|
||||
showPayFundsScreenDisplayed.set(true);
|
||||
updateSpinnerInfo();
|
||||
}
|
||||
@ -659,16 +665,23 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
return dataModel.getTradeCurrency();
|
||||
}
|
||||
|
||||
public String getOfferFee() {
|
||||
return formatter.formatCoinWithCode(dataModel.getCreateOfferFeeAsCoin());
|
||||
}
|
||||
|
||||
public String getTxFee() {
|
||||
return formatter.formatCoinWithCode(dataModel.getTxFeeAsCoin());
|
||||
public String getTradeAmount() {
|
||||
return formatter.formatCoinWithCode(dataModel.amount.get());
|
||||
}
|
||||
|
||||
public String getSecurityDeposit() {
|
||||
return formatter.formatCoinWithCode(dataModel.getSecurityDepositAsCoin());
|
||||
return formatter.formatCoinWithCode(dataModel.getSecurityDepositAsCoin()) +
|
||||
GUIUtil.getPercentageOfTradeAmount(dataModel.getSecurityDepositAsCoin(), dataModel.amount.get(), formatter);
|
||||
}
|
||||
|
||||
public String getCreateOfferFee() {
|
||||
return formatter.formatCoinWithCode(dataModel.getCreateOfferFeeAsCoin()) +
|
||||
GUIUtil.getPercentageOfTradeAmount(dataModel.getCreateOfferFeeAsCoin(), dataModel.amount.get(), formatter);
|
||||
}
|
||||
|
||||
public String getTxFee() {
|
||||
return formatter.formatCoinWithCode(dataModel.getTxFeeAsCoin()) +
|
||||
GUIUtil.getPercentageOfTradeAmount(dataModel.getTxFeeAsCoin(), dataModel.amount.get(), formatter);
|
||||
}
|
||||
|
||||
public PaymentAccount getPaymentAccount() {
|
||||
@ -715,14 +728,15 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
|
||||
private void setAmountToModel() {
|
||||
if (amount.get() != null && !amount.get().isEmpty()) {
|
||||
dataModel.amount.set(formatter.parseToCoinWith4Decimals(amount.get()));
|
||||
dataModel.setAmount(formatter.parseToCoinWith4Decimals(amount.get()));
|
||||
if (dataModel.minAmount.get() == null || dataModel.minAmount.get().equals(Coin.ZERO)) {
|
||||
minAmount.set(amount.get());
|
||||
setMinAmountToModel();
|
||||
}
|
||||
} else {
|
||||
dataModel.amount.set(null);
|
||||
dataModel.setAmount(null);
|
||||
}
|
||||
dataModel.updateTradeFee();
|
||||
}
|
||||
|
||||
private void setMinAmountToModel() {
|
||||
|
@ -26,6 +26,7 @@ import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.btc.provider.price.PriceFeedService;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.btc.wallet.TradeWalletService;
|
||||
import io.bitsquare.common.util.Utilities;
|
||||
import io.bitsquare.gui.common.model.ActivatableDataModel;
|
||||
import io.bitsquare.gui.main.overlays.notifications.Notification;
|
||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||
@ -174,7 +175,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
|
||||
// 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
|
||||
// The mining fee for the takeOfferFee tx is deducted from the takeOfferFee and not visible to the trader
|
||||
|
||||
// The taker pays the mining fee for the trade fee tx and the trade txs.
|
||||
// A typical trade fee tx has about 226 bytes (if one input). The trade txs has about 336-414 bytes.
|
||||
@ -190,19 +191,16 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
// trade fee tx: 226 bytes (1 input) - 374 bytes (2 inputs)
|
||||
// deposit tx: 336 bytes (1 MS output+ OP_RETURN) - 414 bytes (1 MS output + OP_RETURN + change in case of smaller trade amount)
|
||||
// payout tx: 371 bytes
|
||||
// disputed payout tx: 408 bytes
|
||||
feeService.requestFees(() -> {
|
||||
//TODO update doubleTxFeeAsCoin and txFeeAsCoin in view with binding
|
||||
takerFeeAsCoin = feeService.getTakeOfferFee();
|
||||
txFeeAsCoin = feeService.getTxFee(400);
|
||||
totalTxFeeAsCoin = txFeeAsCoin.multiply(3);
|
||||
calculateTotalToPay();
|
||||
}, null);
|
||||
// disputed payout tx: 408 bytes
|
||||
|
||||
takerFeeAsCoin = feeService.getTakeOfferFee();
|
||||
// Set the default values (in rare cases if the fee request was not done yet we get the hard coded default values)
|
||||
// But the "take offer" happens usually after that so we should have already the value from the estimation service.
|
||||
txFeeAsCoin = feeService.getTxFee(400);
|
||||
totalTxFeeAsCoin = txFeeAsCoin.multiply(3);
|
||||
|
||||
// We request to get the actual estimated fee
|
||||
requestTxFee();
|
||||
|
||||
calculateVolume();
|
||||
calculateTotalToPay();
|
||||
|
||||
@ -244,6 +242,14 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
priceFeedService.setCurrencyCode(offer.getCurrencyCode());
|
||||
}
|
||||
|
||||
void requestTxFee() {
|
||||
feeService.requestFees(() -> {
|
||||
txFeeAsCoin = feeService.getTxFee(400);
|
||||
totalTxFeeAsCoin = txFeeAsCoin.multiply(3);
|
||||
calculateTotalToPay();
|
||||
}, null);
|
||||
}
|
||||
|
||||
void onTabSelected(boolean isSelected) {
|
||||
this.isTabSelected = isSelected;
|
||||
if (!preferences.getUseStickyMarketPrice() && isTabSelected)
|
||||
@ -258,11 +264,13 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
// errorMessageHandler is used only in the check availability phase. As soon we have a trade we write the error msg in the trade object as we want to
|
||||
// have it persisted as well.
|
||||
void onTakeOffer(TradeResultHandler tradeResultHandler) {
|
||||
checkNotNull(totalTxFeeAsCoin, "totalTxFeeAsCoin must not be null");
|
||||
Coin fundsNeededForTrade = totalToPayAsCoin.get().subtract(takerFeeAsCoin).subtract(txFeeAsCoin);
|
||||
tradeManager.onTakeOffer(amountAsCoin.get(),
|
||||
txFeeAsCoin,
|
||||
takerFeeAsCoin,
|
||||
tradePrice.getValue(),
|
||||
totalToPayAsCoin.get().subtract(takerFeeAsCoin).subtract(txFeeAsCoin),
|
||||
fundsNeededForTrade,
|
||||
offer,
|
||||
paymentAccount.getId(),
|
||||
useSavingsWallet,
|
||||
@ -343,8 +351,12 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
}
|
||||
}
|
||||
|
||||
void setAmount(Coin amount) {
|
||||
void applyAmount(Coin amount) {
|
||||
amountAsCoin.set(amount);
|
||||
|
||||
takerFeeAsCoin = Utilities.getFeePerBtc(feeService.getTakeOfferFeeInBtcPerBtc(), amount);
|
||||
takerFeeAsCoin = Utilities.maxCoin(takerFeeAsCoin, feeService.getMinTakeOfferFeeInBtc());
|
||||
|
||||
calculateTotalToPay();
|
||||
}
|
||||
|
||||
@ -352,7 +364,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
// 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
|
||||
if (offer != null && amountAsCoin.get() != null) {
|
||||
if (offer != null && amountAsCoin.get() != null && takerFeeAsCoin != null) {
|
||||
Coin value = takerFeeAsCoin.add(totalTxFeeAsCoin).add(securityDepositAsCoin);
|
||||
if (getDirection() == Offer.Direction.SELL)
|
||||
totalToPayAsCoin.set(value);
|
||||
@ -453,6 +465,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
}
|
||||
|
||||
public Coin getTakerFeeAsCoin() {
|
||||
checkNotNull(totalTxFeeAsCoin, "totalTxFeeAsCoin must not be null");
|
||||
return takerFeeAsCoin;
|
||||
}
|
||||
|
||||
@ -460,6 +473,10 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
return totalTxFeeAsCoin;
|
||||
}
|
||||
|
||||
public Coin getTxFeeAsCoin() {
|
||||
return txFeeAsCoin;
|
||||
}
|
||||
|
||||
public AddressEntry getAddressEntry() {
|
||||
return addressEntry;
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
directionLabel.setId("direction-icon-label-buy");
|
||||
|
||||
takeOfferButton.setId("buy-button-big");
|
||||
takeOfferButton.setText("Review offer to buy bitcoin");
|
||||
takeOfferButton.setText("Review: Take offer to buy bitcoin");
|
||||
nextButton.setId("buy-button");
|
||||
} else {
|
||||
imageView.setId("image-sell-large");
|
||||
@ -231,7 +231,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
|
||||
takeOfferButton.setId("sell-button-big");
|
||||
nextButton.setId("sell-button");
|
||||
takeOfferButton.setText("Review offer to sell bitcoin");
|
||||
takeOfferButton.setText("Review: Take offer to sell bitcoin");
|
||||
}
|
||||
|
||||
boolean showComboBox = model.getPossiblePaymentAccounts().size() > 1;
|
||||
@ -340,7 +340,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
.show();
|
||||
|
||||
key = "takeOfferFundWalletInfo";
|
||||
String tradeAmountText = model.isSeller() ? "- Trade amount: " + model.getAmount() + "\n" : "";
|
||||
String tradeAmountText = model.isSeller() ? "- Trade amount: " + model.getTradeAmount() + "\n" : "";
|
||||
new Popup().headLine("Fund your trade").instruction("You need to deposit " +
|
||||
model.totalToPay.get() + " for taking this offer.\n\n" +
|
||||
|
||||
@ -348,7 +348,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
tradeAmountText +
|
||||
"- Security deposit: " + model.getSecurityDeposit() + "\n" +
|
||||
"- Trading fee: " + model.getTakerFee() + "\n" +
|
||||
"- Bitcoin mining fee: " + model.getNetworkFee() + "\n\n" +
|
||||
"- Mining fee (3x): " + 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" +
|
||||
@ -977,11 +977,11 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
|
||||
int i = 0;
|
||||
if (model.isSeller())
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.tradeAmount"), model.getAmount());
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.tradeAmount"), model.getTradeAmount());
|
||||
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.securityDeposit"), model.getSecurityDeposit());
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.offerFee"), model.getTakerFee());
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.networkFee"), model.getNetworkFee());
|
||||
addPayInfoEntry(infoGridPane, i++, BSResources.get("takeOffer.fundsBox.networkFee"), model.getTxFee());
|
||||
Separator separator = new Separator();
|
||||
separator.setOrientation(Orientation.HORIZONTAL);
|
||||
separator.setStyle("-fx-background: #666;");
|
||||
@ -1002,6 +1002,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
private void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) {
|
||||
Label label = new Label(labelText);
|
||||
TextField textField = new TextField(value);
|
||||
textField.setMinWidth(300);
|
||||
textField.setEditable(false);
|
||||
textField.setFocusTraversable(false);
|
||||
textField.setId("payment-info");
|
||||
|
@ -27,6 +27,7 @@ import io.bitsquare.gui.main.funds.FundsView;
|
||||
import io.bitsquare.gui.main.funds.deposit.DepositView;
|
||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import io.bitsquare.gui.util.GUIUtil;
|
||||
import io.bitsquare.gui.util.validation.BtcValidator;
|
||||
import io.bitsquare.gui.util.validation.InputValidator;
|
||||
import io.bitsquare.locale.BSResources;
|
||||
@ -208,6 +209,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
}
|
||||
|
||||
public void onShowPayFundsScreen() {
|
||||
dataModel.requestTxFee();
|
||||
showPayFundsScreenDisplayed.set(true);
|
||||
updateSpinnerInfo();
|
||||
}
|
||||
@ -525,7 +527,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
}
|
||||
|
||||
private void setAmountToModel() {
|
||||
dataModel.setAmount(formatter.parseToCoinWith4Decimals(amount.get()));
|
||||
dataModel.applyAmount(formatter.parseToCoinWith4Decimals(amount.get()));
|
||||
}
|
||||
|
||||
|
||||
@ -569,22 +571,26 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
return amountDescription;
|
||||
}
|
||||
|
||||
String getAmount() {
|
||||
String getTradeAmount() {
|
||||
return formatter.formatCoinWithCode(dataModel.amountAsCoin.get());
|
||||
}
|
||||
|
||||
String getTakerFee() {
|
||||
return formatter.formatCoinWithCode(dataModel.getTakerFeeAsCoin());
|
||||
}
|
||||
|
||||
String getNetworkFee() {
|
||||
return formatter.formatCoinWithCode(dataModel.getTotalTxFeeAsCoin());
|
||||
}
|
||||
|
||||
public String getSecurityDeposit() {
|
||||
return formatter.formatCoinWithCode(dataModel.getSecurityDepositAsCoin());
|
||||
return formatter.formatCoinWithCode(dataModel.getSecurityDepositAsCoin()) +
|
||||
GUIUtil.getPercentageOfTradeAmount(dataModel.getSecurityDepositAsCoin(), dataModel.amountAsCoin.get(), formatter);
|
||||
}
|
||||
|
||||
public String getTakerFee() {
|
||||
return formatter.formatCoinWithCode(dataModel.getTakerFeeAsCoin()) +
|
||||
GUIUtil.getPercentageOfTradeAmount(dataModel.getTakerFeeAsCoin(), dataModel.amountAsCoin.get(), formatter);
|
||||
}
|
||||
|
||||
public String getTxFee() {
|
||||
return formatter.formatCoinWithCode(dataModel.getTotalTxFeeAsCoin()) +
|
||||
GUIUtil.getPercentageOfTradeAmount(dataModel.getTotalTxFeeAsCoin(), dataModel.amountAsCoin.get(), formatter);
|
||||
}
|
||||
|
||||
|
||||
public PaymentMethod getPaymentMethod() {
|
||||
return dataModel.getPaymentMethod();
|
||||
}
|
||||
|
@ -178,8 +178,8 @@ public class ContractWindow extends Overlay<ContractWindow> {
|
||||
viewContractButton.setOnAction(e -> {
|
||||
TextArea textArea = new TextArea();
|
||||
String contractAsJson = dispute.getContractAsJson();
|
||||
contractAsJson += "\n\nBuyerPubKeyHex: " + Utils.HEX.encode(dispute.getContract().getBuyerBtcPubKey());
|
||||
contractAsJson += "\nSellerPubKeyHex: " + Utils.HEX.encode(dispute.getContract().getSellerBtcPubKey());
|
||||
contractAsJson += "\n\nBuyerMultiSigPubKeyAsHex: " + Utils.HEX.encode(dispute.getContract().getBuyerMultiSigPubKey());
|
||||
contractAsJson += "\nSellerMultiSigPubKeyAsHex: " + Utils.HEX.encode(dispute.getContract().getSellerMultiSigPubKey());
|
||||
textArea.setText(contractAsJson);
|
||||
textArea.setPrefHeight(50);
|
||||
textArea.setEditable(false);
|
||||
|
@ -606,9 +606,10 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
||||
disputeResult.getArbitratorPayoutAmount(),
|
||||
contract.getBuyerPayoutAddressString(),
|
||||
contract.getSellerPayoutAddressString(),
|
||||
arbitratorAddressEntry,
|
||||
contract.getBuyerBtcPubKey(),
|
||||
contract.getSellerBtcPubKey(),
|
||||
arbitratorAddressEntry.getAddressString(),
|
||||
arbitratorAddressEntry.getKeyPair(),
|
||||
contract.getBuyerMultiSigPubKey(),
|
||||
contract.getSellerMultiSigPubKey(),
|
||||
arbitratorAddressEntry.getPubKey()
|
||||
);
|
||||
disputeResult.setArbitratorSignature(arbitratorSignature);
|
||||
|
@ -296,8 +296,8 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
||||
boolean isBuyOffer = offer.getDirection() == Offer.Direction.BUY;
|
||||
boolean isBuyerRole = isPlaceOffer ? isBuyOffer : !isBuyOffer;
|
||||
|
||||
String placeOfferButtonText = isBuyerRole ? "Confirm offer to buy bitcoin" : "Confirm offer to sell bitcoin";
|
||||
String takeOfferButtonText = isBuyerRole ? "Confirm offer to buy bitcoin" : "Confirm offer to sell bitcoin";
|
||||
String placeOfferButtonText = isBuyerRole ? "Confirm: Place offer to buy bitcoin" : "Confirm: Place offer to sell bitcoin";
|
||||
String takeOfferButtonText = isBuyerRole ? "Confirm: Take offer to buy bitcoin" : "Confirm: Take offer to sell bitcoin";
|
||||
|
||||
ImageView iconView = new ImageView();
|
||||
iconView.setId(isBuyerRole ? "image-buy-white" : "image-sell-white");
|
||||
|
@ -50,7 +50,7 @@ public class ShowWalletDataWindow extends Overlay<ShowWalletDataWindow> {
|
||||
if (headLine == null)
|
||||
headLine = "Wallet data";
|
||||
|
||||
width = 1000;
|
||||
width = 1200;
|
||||
createGridPane();
|
||||
addHeadLine();
|
||||
addSeparator();
|
||||
@ -82,6 +82,7 @@ public class ShowWalletDataWindow extends Overlay<ShowWalletDataWindow> {
|
||||
Label label = labelTextAreaTuple2.first;
|
||||
label.setMinWidth(150);
|
||||
textArea.setPrefHeight(500);
|
||||
textArea.setStyle("-fx-font-size: 10;");
|
||||
CheckBox isUpdateCheckBox = addLabelCheckBox(gridPane, ++rowIndex, "Include private keys:", "").second;
|
||||
isUpdateCheckBox.setSelected(false);
|
||||
|
||||
|
@ -35,9 +35,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static io.bitsquare.gui.util.FormBuilder.addLabelInputTextField;
|
||||
|
||||
@ -106,11 +103,6 @@ public class SpendFromDepositTxWindow extends Overlay<SpendFromDepositTxWindow>
|
||||
InputTextField arbitratorPubKeyAsHex = addLabelInputTextField(gridPane, ++rowIndex, "arbitratorPubKeyAsHex:").second;
|
||||
|
||||
InputTextField P2SHMultiSigOutputScript = addLabelInputTextField(gridPane, ++rowIndex, "P2SHMultiSigOutputScript:").second;
|
||||
InputTextField buyerPubKeysInputTextField = addLabelInputTextField(gridPane, ++rowIndex, "buyerPubKeys:").second;
|
||||
InputTextField sellerPubKeysInputTextField = addLabelInputTextField(gridPane, ++rowIndex, "sellerPubKeys:").second;
|
||||
|
||||
List<String> buyerPubKeys = !buyerPubKeysInputTextField.getText().isEmpty() ? Arrays.asList(buyerPubKeysInputTextField.getText().split(",")) : new ArrayList<>();
|
||||
List<String> sellerPubKeys = !sellerPubKeysInputTextField.getText().isEmpty() ? Arrays.asList(sellerPubKeysInputTextField.getText().split(",")) : new ArrayList<>();
|
||||
|
||||
|
||||
// Notes:
|
||||
@ -146,15 +138,8 @@ public class SpendFromDepositTxWindow extends Overlay<SpendFromDepositTxWindow>
|
||||
|
||||
P2SHMultiSigOutputScript.setText("");
|
||||
|
||||
sellerPubKeys = Arrays.asList();
|
||||
|
||||
buyerPubKeys = Arrays.asList();
|
||||
|
||||
|
||||
actionButtonText("Sign and publish transaction");
|
||||
|
||||
final List<String> finalSellerPubKeys = sellerPubKeys;
|
||||
final List<String> finalBuyerPubKeys = buyerPubKeys;
|
||||
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction result) {
|
||||
@ -191,8 +176,6 @@ public class SpendFromDepositTxWindow extends Overlay<SpendFromDepositTxWindow>
|
||||
sellerPubKeyAsHex.getText(),
|
||||
arbitratorPubKeyAsHex.getText(),
|
||||
P2SHMultiSigOutputScript.getText(),
|
||||
finalBuyerPubKeys,
|
||||
finalSellerPubKeys,
|
||||
callback);
|
||||
} catch (AddressFormatException | WalletException | TransactionVerificationException e) {
|
||||
log.error(e.toString());
|
||||
|
@ -18,7 +18,6 @@
|
||||
package io.bitsquare.gui.main.overlays.windows;
|
||||
|
||||
import io.bitsquare.arbitration.DisputeManager;
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.gui.components.TextFieldWithCopyIcon;
|
||||
import io.bitsquare.gui.main.MainView;
|
||||
import io.bitsquare.gui.main.overlays.Overlay;
|
||||
@ -43,6 +42,7 @@ import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.stage.Window;
|
||||
import org.bitcoinj.core.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -131,7 +131,7 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
|
||||
addLabelTextField(gridPane, ++rowIndex, "Payment method:", BSResources.get(offer.getPaymentMethod().getId()));
|
||||
|
||||
// second group
|
||||
rows = 5;
|
||||
rows = 6;
|
||||
PaymentAccountContractData buyerPaymentAccountContractData = null;
|
||||
PaymentAccountContractData sellerPaymentAccountContractData = null;
|
||||
|
||||
@ -173,6 +173,7 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
|
||||
addLabelTextFieldWithCopyIcon(gridPane, rowIndex, "Trade ID:", trade.getId(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
|
||||
addLabelTextField(gridPane, ++rowIndex, "Trade date:", formatter.formatDateTime(trade.getDate()));
|
||||
addLabelTextField(gridPane, ++rowIndex, "Security deposit:", formatter.formatCoinWithCode(offer.getSecurityDeposit()));
|
||||
addLabelTextField(gridPane, ++rowIndex, "Tx fee:", formatter.formatCoinWithCode(trade.getTxFee()));
|
||||
addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Selected arbitrator:", trade.getArbitratorNodeAddress().getFullAddress());
|
||||
|
||||
if (trade.getTradingPeerNodeAddress() != null)
|
||||
@ -207,7 +208,10 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
|
||||
viewContractButton.setDefaultButton(false);
|
||||
viewContractButton.setOnAction(e -> {
|
||||
TextArea textArea = new TextArea();
|
||||
textArea.setText(trade.getContractAsJson());
|
||||
String contractAsJson = trade.getContractAsJson();
|
||||
contractAsJson += "\n\nBuyerMultiSigPubKeyAsHex: " + Utils.HEX.encode(trade.getContract().getBuyerMultiSigPubKey());
|
||||
contractAsJson += "\nSellerMultiSigPubKeyAsHex: " + Utils.HEX.encode(trade.getContract().getSellerMultiSigPubKey());
|
||||
textArea.setText(contractAsJson);
|
||||
textArea.setPrefHeight(50);
|
||||
textArea.setEditable(false);
|
||||
textArea.setWrapText(true);
|
||||
|
@ -29,7 +29,7 @@
|
||||
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn text="Trade ID" fx:id="tradeIdColumn" minWidth="120" maxWidth="120"/>
|
||||
<TableColumn text="Date" fx:id="dateColumn" minWidth="130"/>
|
||||
<TableColumn text="Date" fx:id="dateColumn" minWidth="180"/>
|
||||
<TableColumn text="Market" fx:id="marketColumn" minWidth="100"/>
|
||||
<TableColumn text="Price" fx:id="priceColumn" minWidth="100"/>
|
||||
<TableColumn text="Amount in BTC" fx:id="amountColumn" minWidth="130"/>
|
||||
|
@ -29,7 +29,7 @@
|
||||
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn text="Offer ID" fx:id="offerIdColumn" minWidth="120" maxWidth="130"/>
|
||||
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="180" maxWidth="190"/>
|
||||
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="200"/>
|
||||
<TableColumn text="Market" fx:id="marketColumn" minWidth="100"/>
|
||||
<TableColumn text="Price" fx:id="priceColumn" minWidth="160"/>
|
||||
<TableColumn text="BTC (min - max)" fx:id="amountColumn" minWidth="160"/>
|
||||
|
@ -39,6 +39,7 @@ import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.StringConverter;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -300,4 +301,9 @@ public class GUIUtil {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getPercentageOfTradeAmount(Coin fee, Coin tradeAmount, BSFormatter formatter) {
|
||||
return " (" + formatter.formatToPercentWithSymbol((double) fee.value / (double) tradeAmount.value) +
|
||||
" of trade amount)";
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,12 @@ public final class AltCoinAddressValidator extends InputValidator {
|
||||
ValidationResult regexTestFailed = new ValidationResult(false, "Address validation failed because it does not match the structure of a " + currencyCode + " address.");
|
||||
|
||||
switch (currencyCode) {
|
||||
case "ETH":
|
||||
// https://github.com/ethereum/web3.js/blob/master/lib/utils/utils.js#L403
|
||||
if (!input.matches("^(0x)?[0-9a-fA-F]{40}$")) {
|
||||
return regexTestFailed;
|
||||
}
|
||||
return new ValidationResult(true);
|
||||
// Example for BTC, though for BTC we use the BitcoinJ library address check
|
||||
case "BTC":
|
||||
// taken form: https://stackoverflow.com/questions/21683680/regex-to-match-bitcoin-addresses
|
||||
|
@ -180,7 +180,7 @@ takeOffer.fundsBox.sell.info=For every offer there is a dedicated trade wallet.
|
||||
takeOffer.fundsBox.tradeAmount=Amount to sell:
|
||||
takeOffer.fundsBox.securityDeposit=Security deposit:
|
||||
takeOffer.fundsBox.offerFee=Take offer fee:
|
||||
takeOffer.fundsBox.networkFee=Mining fee:
|
||||
takeOffer.fundsBox.networkFee=Mining fees (3x):
|
||||
takeOffer.fundsBox.total=Total:
|
||||
takeOffer.fundsBox.showAdvanced=Show advanced settings
|
||||
takeOffer.fundsBox.hideAdvanced=Hide advanced settings
|
||||
|
@ -92,4 +92,17 @@ public class AltCoinAddressValidatorTest {
|
||||
assertFalse(validator.validate("XGKZODTTTRXIUA75TKONWHFDCU6634DZ").isValid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testETH() {
|
||||
AltCoinAddressValidator validator = new AltCoinAddressValidator();
|
||||
validator.setCurrencyCode("ETH");
|
||||
|
||||
assertTrue(validator.validate("0x2a65Aca4D5fC5B5C859090a6c34d164135398226").isValid);
|
||||
assertTrue(validator.validate("2a65Aca4D5fC5B5C859090a6c34d164135398226").isValid);
|
||||
|
||||
assertFalse(validator.validate("0x2a65Aca4D5fC5B5C859090a6c34d1641353982266").isValid);
|
||||
assertFalse(validator.validate("0x2a65Aca4D5fC5B5C859090a6c34d16413539822g").isValid);
|
||||
assertFalse(validator.validate("2a65Aca4D5fC5B5C859090a6c34d16413539822g").isValid);
|
||||
assertFalse(validator.validate("").isValid);
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
//TODO use protobuffer instead of json
|
||||
public class FeeRequestService {
|
||||
private static final Logger log = LoggerFactory.getLogger(FeeRequestService.class);
|
||||
|
||||
@ -47,10 +48,7 @@ public class FeeRequestService {
|
||||
public FeeRequestService() throws IOException {
|
||||
btcFeesProvider = new BtcFeesProvider();
|
||||
|
||||
allFeesMap.put("txFee", FeeService.DEFAULT_TX_FEE);
|
||||
allFeesMap.put("createOfferFee", FeeService.DEFAULT_CREATE_OFFER_FEE);
|
||||
allFeesMap.put("takeOfferFee", FeeService.DEFAULT_TAKE_OFFER_FEE);
|
||||
|
||||
writeToJson();
|
||||
startRequests();
|
||||
}
|
||||
|
||||
@ -101,5 +99,4 @@ public class FeeRequestService {
|
||||
public String getJson() {
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
//TODO use protobuffer instead of json
|
||||
public class BtcFeesProvider {
|
||||
private static final Logger log = LoggerFactory.getLogger(BtcFeesProvider.class);
|
||||
|
||||
@ -23,7 +24,7 @@ public class BtcFeesProvider {
|
||||
|
||||
public Long getFee() throws IOException, HttpException {
|
||||
String response = httpClient.requestWithGET("recommended", "User-Agent", "");
|
||||
log.debug("Get recommended fee response: " + response);
|
||||
log.info("Get recommended fee response: " + response);
|
||||
Map<String, Long> map = new HashMap<>();
|
||||
LinkedTreeMap<String, Double> treeMap = new Gson().fromJson(response, LinkedTreeMap.class);
|
||||
treeMap.entrySet().stream().forEach(e -> map.put(e.getKey(), e.getValue().longValue()));
|
||||
|
Loading…
Reference in New Issue
Block a user