Merge branch 'DAO' into policyfile

This commit is contained in:
Mike Rosseel 2017-02-20 15:31:35 +01:00
commit f531659bf2
62 changed files with 761 additions and 524 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -95,6 +95,6 @@ public final class AddressEntryList extends ArrayList<AddressEntry> implements P
}
public void queueUpForSave() {
storage.queueUpForSave();
storage.queueUpForSave(50);
}
}

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

@ -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(),

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ public class SendPayoutTxFinalizedMessage extends TradeTask {
new PayoutTxFinalizedMessage(
processModel.getId(),
trade.getPayoutTx().bitcoinSerialize(),
processModel.getMyAddress()
processModel.getMyNodeAddress()
),
new SendMailboxMessageListener() {
@Override

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(),

View File

@ -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(),

View File

@ -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(),

View File

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

View File

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

View File

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

View File

@ -71,7 +71,7 @@ class LockedListItem {
}
private void updateBalance() {
balance = addressEntry.getLockedTradeAmount();
balance = addressEntry.getCoinLockedInMultiSig();
if (balance != null)
balanceLabel.setText(formatter.formatCoin(this.balance));
}

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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