diff --git a/core/src/main/java/haveno/core/api/CoreDisputesService.java b/core/src/main/java/haveno/core/api/CoreDisputesService.java index 64755d54..6e28c38f 100644 --- a/core/src/main/java/haveno/core/api/CoreDisputesService.java +++ b/core/src/main/java/haveno/core/api/CoreDisputesService.java @@ -146,9 +146,10 @@ public class CoreDisputesService { synchronized (trade) { try { - var closeDate = new Date(); - var disputeResult = createDisputeResult(winningDispute, winner, reason, summaryNotes, closeDate); + // create dispute result + var closeDate = new Date(); + var winnerDisputeResult = createDisputeResult(winningDispute, winner, reason, summaryNotes, closeDate); DisputePayout payout; if (customWinnerAmount > 0) { payout = DisputePayout.CUSTOM; @@ -159,13 +160,14 @@ public class CoreDisputesService { } else { throw new IllegalStateException("Unexpected DisputeResult.Winner: " + winner); } - applyPayoutAmountsToDisputeResult(payout, winningDispute, disputeResult, customWinnerAmount); + applyPayoutAmountsToDisputeResult(payout, winningDispute, winnerDisputeResult, customWinnerAmount); + winnerDisputeResult.setSubtractFeeFrom(customWinnerAmount == 0 ? DisputeResult.SubtractFeeFrom.BUYER_AND_SELLER : winner == DisputeResult.Winner.BUYER ? DisputeResult.SubtractFeeFrom.SELLER_ONLY : DisputeResult.SubtractFeeFrom.BUYER_ONLY); // create dispute payout tx - trade.getProcessModel().setUnsignedPayoutTx(arbitrationManager.createDisputePayoutTx(trade, winningDispute.getContract(), disputeResult, false)); + trade.getProcessModel().setUnsignedPayoutTx(arbitrationManager.createDisputePayoutTx(trade, winningDispute.getContract(), winnerDisputeResult, false)); // close winning dispute ticket - closeDisputeTicket(arbitrationManager, winningDispute, disputeResult, () -> { + closeDisputeTicket(arbitrationManager, winningDispute, winnerDisputeResult, () -> { arbitrationManager.requestPersistence(); }, (errMessage, err) -> { throw new IllegalStateException(errMessage, err); @@ -178,8 +180,9 @@ public class CoreDisputesService { if (!loserDisputeOptional.isPresent()) throw new IllegalStateException("could not find peer dispute"); var loserDispute = loserDisputeOptional.get(); var loserDisputeResult = createDisputeResult(loserDispute, winner, reason, summaryNotes, closeDate); - loserDisputeResult.setBuyerPayoutAmount(disputeResult.getBuyerPayoutAmount()); - loserDisputeResult.setSellerPayoutAmount(disputeResult.getSellerPayoutAmount()); + loserDisputeResult.setBuyerPayoutAmount(winnerDisputeResult.getBuyerPayoutAmount()); + loserDisputeResult.setSellerPayoutAmount(winnerDisputeResult.getSellerPayoutAmount()); + loserDisputeResult.setSubtractFeeFrom(winnerDisputeResult.getSubtractFeeFrom()); closeDisputeTicket(arbitrationManager, loserDispute, loserDisputeResult, () -> { arbitrationManager.requestPersistence(); }, (errMessage, err) -> { @@ -239,6 +242,7 @@ public class CoreDisputesService { disputeResult.setBuyerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? customWinnerAmount : loserAmount)); disputeResult.setSellerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? loserAmount : customWinnerAmount)); } + disputeResult.setSubtractFeeFrom(DisputeResult.SubtractFeeFrom.BUYER_AND_SELLER); // TODO: can extend UI to specify who pays mining fee } public void closeDisputeTicket(DisputeManager disputeManager, Dispute dispute, DisputeResult disputeResult, ResultHandler resultHandler, FaultHandler faultHandler) { diff --git a/core/src/main/java/haveno/core/api/CoreOffersService.java b/core/src/main/java/haveno/core/api/CoreOffersService.java index 65e9e9f0..4fa8017a 100644 --- a/core/src/main/java/haveno/core/api/CoreOffersService.java +++ b/core/src/main/java/haveno/core/api/CoreOffersService.java @@ -243,7 +243,7 @@ public class CoreOffersService { for (Offer offer2 : offers) { if (offer == offer2) continue; if (offer2.getOfferPayload().getReserveTxKeyImages().contains(keyImage)) { - log.warn("Key image {} belongs to multiple offers, seen in offer {}", keyImage, offer2.getId()); + log.warn("Key image {} belongs to multiple offers, seen in offer {} and {}", keyImage, offer.getId(), offer2.getId()); duplicateFundedOffers.add(offer2); } } diff --git a/core/src/main/java/haveno/core/api/CoreTradesService.java b/core/src/main/java/haveno/core/api/CoreTradesService.java index 25a97420..4b83bf44 100644 --- a/core/src/main/java/haveno/core/api/CoreTradesService.java +++ b/core/src/main/java/haveno/core/api/CoreTradesService.java @@ -134,7 +134,7 @@ class CoreTradesService { takeOfferModel.initModel(offer, paymentAccount, amount, useSavingsWallet); takerFee = takeOfferModel.getTakerFee(); fundsNeededForTrade = takeOfferModel.getFundsNeededForTrade(); - log.info("Initiating take {} offer, {}", offer.isBuyOffer() ? "buy" : "sell", takeOfferModel); + log.debug("Initiating take {} offer, {}", offer.isBuyOffer() ? "buy" : "sell", takeOfferModel); } // take offer diff --git a/core/src/main/java/haveno/core/api/model/OfferInfo.java b/core/src/main/java/haveno/core/api/model/OfferInfo.java index cb8f3d28..b14c63d1 100644 --- a/core/src/main/java/haveno/core/api/model/OfferInfo.java +++ b/core/src/main/java/haveno/core/api/model/OfferInfo.java @@ -74,6 +74,9 @@ public class OfferInfo implements Payload { private final int protocolVersion; @Nullable private final String arbitratorSigner; + @Nullable + private final String splitOutputTxHash; + private final long splitOutputTxFee; public OfferInfo(OfferInfoBuilder builder) { this.id = builder.getId(); @@ -103,6 +106,8 @@ public class OfferInfo implements Payload { this.versionNumber = builder.getVersionNumber(); this.protocolVersion = builder.getProtocolVersion(); this.arbitratorSigner = builder.getArbitratorSigner(); + this.splitOutputTxHash = builder.getSplitOutputTxHash(); + this.splitOutputTxFee = builder.getSplitOutputTxFee(); } public static OfferInfo toOfferInfo(Offer offer) { @@ -127,6 +132,8 @@ public class OfferInfo implements Payload { .withTriggerPrice(preciseTriggerPrice) .withState(openOffer.getState().name()) .withIsActivated(isActivated) + .withSplitOutputTxHash(openOffer.getSplitOutputTxHash()) + .withSplitOutputTxFee(openOffer.getSplitOutputTxFee()) .build(); } @@ -199,8 +206,10 @@ public class OfferInfo implements Payload { .setOwnerNodeAddress(ownerNodeAddress) .setPubKeyRing(pubKeyRing) .setVersionNr(versionNumber) - .setProtocolVersion(protocolVersion); + .setProtocolVersion(protocolVersion) + .setSplitOutputTxFee(splitOutputTxFee); Optional.ofNullable(arbitratorSigner).ifPresent(builder::setArbitratorSigner); + Optional.ofNullable(splitOutputTxHash).ifPresent(builder::setSplitOutputTxHash); return builder.build(); } @@ -234,6 +243,8 @@ public class OfferInfo implements Payload { .withVersionNumber(proto.getVersionNr()) .withProtocolVersion(proto.getProtocolVersion()) .withArbitratorSigner(proto.getArbitratorSigner()) + .withSplitOutputTxHash(proto.getSplitOutputTxHash()) + .withSplitOutputTxFee(proto.getSplitOutputTxFee()) .build(); } } diff --git a/core/src/main/java/haveno/core/api/model/builder/OfferInfoBuilder.java b/core/src/main/java/haveno/core/api/model/builder/OfferInfoBuilder.java index 2cce4aaa..246bed1d 100644 --- a/core/src/main/java/haveno/core/api/model/builder/OfferInfoBuilder.java +++ b/core/src/main/java/haveno/core/api/model/builder/OfferInfoBuilder.java @@ -59,6 +59,8 @@ public final class OfferInfoBuilder { private String versionNumber; private int protocolVersion; private String arbitratorSigner; + private String splitOutputTxHash; + private long splitOutputTxFee; public OfferInfoBuilder withId(String id) { this.id = id; @@ -209,6 +211,16 @@ public final class OfferInfoBuilder { this.arbitratorSigner = arbitratorSigner; return this; } + + public OfferInfoBuilder withSplitOutputTxHash(String splitOutputTxHash) { + this.splitOutputTxHash = splitOutputTxHash; + return this; + } + + public OfferInfoBuilder withSplitOutputTxFee(long splitOutputTxFee) { + this.splitOutputTxFee = splitOutputTxFee; + return this; + } public OfferInfo build() { return new OfferInfo(this); diff --git a/core/src/main/java/haveno/core/offer/OfferUtil.java b/core/src/main/java/haveno/core/offer/OfferUtil.java index ac877977..6eca9ab4 100644 --- a/core/src/main/java/haveno/core/offer/OfferUtil.java +++ b/core/src/main/java/haveno/core/offer/OfferUtil.java @@ -222,7 +222,7 @@ public class OfferUtil { getMaxBuyerSecurityDepositAsPercent()); checkArgument(buyerSecurityDeposit >= getMinBuyerSecurityDepositAsPercent(), "securityDeposit must not be less than " + - getMinBuyerSecurityDepositAsPercent()); + getMinBuyerSecurityDepositAsPercent() + " but was " + buyerSecurityDeposit); checkArgument(!filterManager.isCurrencyBanned(currencyCode), Res.get("offerbook.warning.currencyBanned")); checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()), diff --git a/core/src/main/java/haveno/core/offer/OpenOffer.java b/core/src/main/java/haveno/core/offer/OpenOffer.java index 0811cb7b..6fce9212 100644 --- a/core/src/main/java/haveno/core/offer/OpenOffer.java +++ b/core/src/main/java/haveno/core/offer/OpenOffer.java @@ -70,6 +70,9 @@ public final class OpenOffer implements Tradable { @Getter @Nullable String splitOutputTxHash; + @Getter + @Setter + long splitOutputTxFee; @Nullable @Setter @Getter @@ -114,6 +117,7 @@ public final class OpenOffer implements Tradable { this.scheduledAmount = openOffer.scheduledAmount; this.scheduledTxHashes = openOffer.scheduledTxHashes == null ? null : new ArrayList(openOffer.scheduledTxHashes); this.splitOutputTxHash = openOffer.splitOutputTxHash; + this.splitOutputTxFee = openOffer.splitOutputTxFee; this.reserveTxHash = openOffer.reserveTxHash; this.reserveTxHex = openOffer.reserveTxHex; this.reserveTxKey = openOffer.reserveTxKey; @@ -130,6 +134,7 @@ public final class OpenOffer implements Tradable { @Nullable String scheduledAmount, @Nullable List scheduledTxHashes, String splitOutputTxHash, + long splitOutputTxFee, @Nullable String reserveTxHash, @Nullable String reserveTxHex, @Nullable String reserveTxKey) { @@ -139,6 +144,7 @@ public final class OpenOffer implements Tradable { this.reserveExactAmount = reserveExactAmount; this.scheduledTxHashes = scheduledTxHashes; this.splitOutputTxHash = splitOutputTxHash; + this.splitOutputTxFee = splitOutputTxFee; this.reserveTxHash = reserveTxHash; this.reserveTxHex = reserveTxHex; this.reserveTxKey = reserveTxKey; @@ -153,6 +159,7 @@ public final class OpenOffer implements Tradable { .setOffer(offer.toProtoMessage()) .setTriggerPrice(triggerPrice) .setState(protobuf.OpenOffer.State.valueOf(state.name())) + .setSplitOutputTxFee(splitOutputTxFee) .setReserveExactAmount(reserveExactAmount); Optional.ofNullable(scheduledAmount).ifPresent(e -> builder.setScheduledAmount(scheduledAmount)); @@ -173,6 +180,7 @@ public final class OpenOffer implements Tradable { proto.getScheduledAmount(), proto.getScheduledTxHashesList(), ProtoUtil.stringOrNullFromProto(proto.getSplitOutputTxHash()), + proto.getSplitOutputTxFee(), proto.getReserveTxHash(), proto.getReserveTxHex(), proto.getReserveTxKey()); @@ -253,6 +261,9 @@ public final class OpenOffer implements Tradable { ",\n offer=" + offer + ",\n state=" + state + ",\n triggerPrice=" + triggerPrice + + ",\n reserveExactAmount=" + reserveExactAmount + + ",\n scheduledAmount=" + scheduledAmount + + ",\n splitOutputTxFee=" + splitOutputTxFee + "\n}"; } } diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java index a792a417..0d6aa725 100644 --- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java +++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java @@ -1006,8 +1006,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe log.info("Done creating split output tx to fund offer {}", openOffer.getId()); // schedule txs - openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); openOffer.setSplitOutputTxHash(splitOutputTx.getHash()); + openOffer.setSplitOutputTxFee(splitOutputTx.getFee().longValueExact()); + openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); openOffer.setScheduledAmount(openOffer.getOffer().getReserveAmount().toString()); openOffer.setState(OpenOffer.State.SCHEDULED); return splitOutputTx; diff --git a/core/src/main/java/haveno/core/support/dispute/DisputeManager.java b/core/src/main/java/haveno/core/support/dispute/DisputeManager.java index d3c0cb29..9aa1acdd 100644 --- a/core/src/main/java/haveno/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/haveno/core/support/dispute/DisputeManager.java @@ -69,6 +69,7 @@ import java.math.BigInteger; import java.security.KeyPair; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Optional; @@ -869,12 +870,29 @@ public abstract class DisputeManager> extends Sup // add any loss of precision to winner payout winnerPayoutAmount = winnerPayoutAmount.add(trade.getWallet().getUnlockedBalance().subtract(winnerPayoutAmount.add(loserPayoutAmount))); - // create dispute payout tx + // create dispute payout tx config MoneroTxConfig txConfig = new MoneroTxConfig().setAccountIndex(0); + txConfig.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY); if (winnerPayoutAmount.compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(winnerPayoutAddress, winnerPayoutAmount); if (loserPayoutAmount.compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(loserPayoutAddress, loserPayoutAmount); - txConfig.setSubtractFeeFrom(loserPayoutAmount.equals(BigInteger.ZERO) ? 0 : txConfig.getDestinations().size() - 1); // winner only pays fee if loser gets 0 - txConfig.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY); + + // configure who pays mining fee + if (loserPayoutAmount.equals(BigInteger.ZERO)) txConfig.setSubtractFeeFrom(0); // winner pays fee if loser gets 0 + else { + switch (disputeResult.getSubtractFeeFrom()) { + case BUYER_AND_SELLER: + txConfig.setSubtractFeeFrom(Arrays.asList(0, 1)); + break; + case BUYER_ONLY: + txConfig.setSubtractFeeFrom(disputeResult.getWinner() == Winner.BUYER ? 0 : 1); + break; + case SELLER_ONLY: + txConfig.setSubtractFeeFrom(disputeResult.getWinner() == Winner.SELLER ? 0 : 1); + break; + } + } + + // create dispute payout tx MoneroTxWallet payoutTx = null; try { payoutTx = trade.getWallet().createTx(txConfig); diff --git a/core/src/main/java/haveno/core/support/dispute/DisputeResult.java b/core/src/main/java/haveno/core/support/dispute/DisputeResult.java index 2d73a7c9..7929b2b3 100644 --- a/core/src/main/java/haveno/core/support/dispute/DisputeResult.java +++ b/core/src/main/java/haveno/core/support/dispute/DisputeResult.java @@ -61,12 +61,21 @@ public final class DisputeResult implements NetworkPayload { PEER_WAS_LATE } + public enum SubtractFeeFrom { + BUYER_ONLY, + SELLER_ONLY, + BUYER_AND_SELLER + } + private final String tradeId; private final int traderId; @Setter @Nullable private Winner winner; private int reasonOrdinal = Reason.OTHER.ordinal(); + @Setter + @Nullable + private SubtractFeeFrom subtractFeeFrom; private final BooleanProperty tamperProofEvidenceProperty = new SimpleBooleanProperty(); private final BooleanProperty idVerificationProperty = new SimpleBooleanProperty(); private final BooleanProperty screenCastProperty = new SimpleBooleanProperty(); @@ -93,6 +102,7 @@ public final class DisputeResult implements NetworkPayload { int traderId, @Nullable Winner winner, int reasonOrdinal, + @Nullable SubtractFeeFrom subtractFeeFrom, boolean tamperProofEvidence, boolean idVerification, boolean screenCast, @@ -107,6 +117,7 @@ public final class DisputeResult implements NetworkPayload { this.traderId = traderId; this.winner = winner; this.reasonOrdinal = reasonOrdinal; + this.subtractFeeFrom = subtractFeeFrom; this.tamperProofEvidenceProperty.set(tamperProofEvidence); this.idVerificationProperty.set(idVerification); this.screenCastProperty.set(screenCast); @@ -129,6 +140,7 @@ public final class DisputeResult implements NetworkPayload { proto.getTraderId(), ProtoUtil.enumFromProto(DisputeResult.Winner.class, proto.getWinner().name()), proto.getReasonOrdinal(), + ProtoUtil.enumFromProto(DisputeResult.SubtractFeeFrom.class, proto.getSubtractFeeFrom().name()), proto.getTamperProofEvidence(), proto.getIdVerification(), proto.getScreenCast(), @@ -158,6 +170,7 @@ public final class DisputeResult implements NetworkPayload { Optional.ofNullable(arbitratorSignature).ifPresent(arbitratorSignature -> builder.setArbitratorSignature(ByteString.copyFrom(arbitratorSignature))); Optional.ofNullable(arbitratorPubKey).ifPresent(arbitratorPubKey -> builder.setArbitratorPubKey(ByteString.copyFrom(arbitratorPubKey))); Optional.ofNullable(winner).ifPresent(result -> builder.setWinner(protobuf.DisputeResult.Winner.valueOf(winner.name()))); + Optional.ofNullable(subtractFeeFrom).ifPresent(result -> builder.setSubtractFeeFrom(protobuf.DisputeResult.SubtractFeeFrom.valueOf(subtractFeeFrom.name()))); Optional.ofNullable(chatMessage).ifPresent(chatMessage -> builder.setChatMessage(chatMessage.toProtoNetworkEnvelope().getChatMessage())); @@ -201,6 +214,7 @@ public final class DisputeResult implements NetworkPayload { } public void setBuyerPayoutAmount(BigInteger buyerPayoutAmount) { + if (buyerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("buyerPayoutAmount cannot be negative"); this.buyerPayoutAmount = buyerPayoutAmount.longValueExact(); } @@ -209,6 +223,7 @@ public final class DisputeResult implements NetworkPayload { } public void setSellerPayoutAmount(BigInteger sellerPayoutAmount) { + if (sellerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("sellerPayoutAmount cannot be negative"); this.sellerPayoutAmount = sellerPayoutAmount.longValueExact(); } @@ -231,6 +246,7 @@ public final class DisputeResult implements NetworkPayload { ",\n traderId=" + traderId + ",\n winner=" + winner + ",\n reasonOrdinal=" + reasonOrdinal + + ",\n subtractFeeFrom=" + subtractFeeFrom + ",\n tamperProofEvidenceProperty=" + tamperProofEvidenceProperty + ",\n idVerificationProperty=" + idVerificationProperty + ",\n screenCastProperty=" + screenCastProperty + diff --git a/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java index f2ff76ff..ce79347a 100644 --- a/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java @@ -390,9 +390,25 @@ public final class ArbitrationManager extends DisputeManager outputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false).setKeyImage(new MoneroKeyImage(keyImage))); // TODO: will this check tx pool? avoid + if (!outputs.isEmpty()) sum = sum.add(outputs.get(0).getAmount()); + } + return sum; + } + + public BigInteger getReservedAmount() { + if (!isDepositsPublished() || isPayoutPublished()) return BigInteger.valueOf(0); + if (isArbitrator()) { + return getAmount().add(getBuyer().getSecurityDeposit()).add(getSeller().getSecurityDeposit()); // arbitrator reserved balance is sum of amounts sent to multisig + } else { + return isBuyer() ? getBuyer().getSecurityDeposit() : getAmount().add(getSeller().getSecurityDeposit()); + } + } + public Price getPrice() { return Price.valueOf(offer.getCurrencyCode(), price); } diff --git a/core/src/main/java/haveno/core/trade/TradeManager.java b/core/src/main/java/haveno/core/trade/TradeManager.java index 62aeaf7b..25b981f5 100644 --- a/core/src/main/java/haveno/core/trade/TradeManager.java +++ b/core/src/main/java/haveno/core/trade/TradeManager.java @@ -815,6 +815,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId())); + // validate inputs + if (amount.compareTo(offer.getAmount()) > 0) throw new RuntimeException("Trade amount exceeds offer amount"); + if (amount.compareTo(offer.getMinAmount()) < 0) throw new RuntimeException("Trade amount is less than minimum offer amount"); + OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser, paymentAccountId, amount); offer.checkOfferAvailability(model, () -> { @@ -998,7 +1002,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi /////////////////////////////////////////////////////////////////////////////////////////// // If trade is in already in critical state (if taker role: taker fee; both roles: after deposit published) - // we move the trade to failedTradesManager + // we move the trade to FailedTradesManager public void onMoveInvalidTradeToFailedTrades(Trade trade) { removeTrade(trade); failedTradesManager.add(trade); diff --git a/core/src/main/java/haveno/core/trade/messages/DepositResponse.java b/core/src/main/java/haveno/core/trade/messages/DepositResponse.java index eac1b36c..f0727bdf 100644 --- a/core/src/main/java/haveno/core/trade/messages/DepositResponse.java +++ b/core/src/main/java/haveno/core/trade/messages/DepositResponse.java @@ -30,15 +30,21 @@ import java.util.Optional; public final class DepositResponse extends TradeMessage implements DirectMessage { private final long currentDate; private final String errorMessage; + private final long buyerSecurityDeposit; + private final long sellerSecurityDeposit; public DepositResponse(String tradeId, String uid, String messageVersion, long currentDate, - String errorMessage) { + String errorMessage, + long buyerSecurityDeposit, + long sellerSecurityDeposit) { super(messageVersion, tradeId, uid); this.currentDate = currentDate; this.errorMessage = errorMessage; + this.buyerSecurityDeposit = buyerSecurityDeposit; + this.sellerSecurityDeposit = sellerSecurityDeposit; } @@ -52,6 +58,8 @@ public final class DepositResponse extends TradeMessage implements DirectMessage .setTradeId(tradeId) .setUid(uid); builder.setCurrentDate(currentDate); + builder.setBuyerSecurityDeposit(buyerSecurityDeposit); + builder.setSellerSecurityDeposit(sellerSecurityDeposit); Optional.ofNullable(errorMessage).ifPresent(e -> builder.setErrorMessage(errorMessage)); return getNetworkEnvelopeBuilder().setDepositResponse(builder).build(); @@ -64,7 +72,9 @@ public final class DepositResponse extends TradeMessage implements DirectMessage proto.getUid(), messageVersion, proto.getCurrentDate(), - ProtoUtil.stringOrNullFromProto(proto.getErrorMessage())); + ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()), + proto.getBuyerSecurityDeposit(), + proto.getSellerSecurityDeposit()); } @Override @@ -72,6 +82,8 @@ public final class DepositResponse extends TradeMessage implements DirectMessage return "DepositResponse {" + ",\n currentDate=" + currentDate + ",\n errorMessage=" + errorMessage + + ",\n buyerSecurityDeposit=" + buyerSecurityDeposit + + ",\n sellerSecurityDeposit=" + sellerSecurityDeposit + "\n} " + super.toString(); } } diff --git a/core/src/main/java/haveno/core/trade/protocol/TradePeer.java b/core/src/main/java/haveno/core/trade/protocol/TradePeer.java index be8b1d8f..af2e0d86 100644 --- a/core/src/main/java/haveno/core/trade/protocol/TradePeer.java +++ b/core/src/main/java/haveno/core/trade/protocol/TradePeer.java @@ -116,6 +116,7 @@ public final class TradePeer implements PersistablePayload { private String depositTxHex; @Nullable private String depositTxKey; + private long depositTxFee; private long securityDeposit; @Nullable private String updatedMultisigHex; @@ -126,7 +127,16 @@ public final class TradePeer implements PersistablePayload { public TradePeer() { } + public BigInteger getDepositTxFee() { + return BigInteger.valueOf(depositTxFee); + } + + public void setDepositTxFee(BigInteger depositTxFee) { + this.depositTxFee = depositTxFee.longValueExact(); + } + public BigInteger getSecurityDeposit() { + if (depositTxHash == null) return null; return BigInteger.valueOf(securityDeposit); } @@ -164,6 +174,7 @@ public final class TradePeer implements PersistablePayload { Optional.ofNullable(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash)); Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex)); Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey)); + Optional.ofNullable(depositTxFee).ifPresent(e -> builder.setDepositTxFee(depositTxFee)); Optional.ofNullable(securityDeposit).ifPresent(e -> builder.setSecurityDeposit(securityDeposit)); Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex)); builder.setDepositsConfirmedMessageAcked(depositsConfirmedMessageAcked); @@ -206,6 +217,7 @@ public final class TradePeer implements PersistablePayload { tradePeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash())); tradePeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex())); tradePeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey())); + tradePeer.setDepositTxFee(BigInteger.valueOf(proto.getDepositTxFee())); tradePeer.setSecurityDeposit(BigInteger.valueOf(proto.getSecurityDeposit())); tradePeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex())); tradePeer.setDepositsConfirmedMessageAcked(proto.getDepositsConfirmedMessageAcked()); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java index 2235397e..1b460098 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java @@ -80,7 +80,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask { boolean isFromTaker = trader == trade.getTaker(); boolean isFromBuyer = trader == trade.getBuyer(); BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee(); - BigInteger sendAmount = isFromBuyer ? BigInteger.valueOf(0) : offer.getAmount(); + BigInteger sendAmount = isFromBuyer ? BigInteger.valueOf(0) : trade.getAmount(); BigInteger securityDeposit = isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit(); String depositAddress = processModel.getMultisigAddress(); @@ -103,6 +103,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask { // set deposit info trader.setSecurityDeposit(txResult.second); + trader.setDepositTxFee(txResult.first.getFee()); trader.setDepositTxHex(request.getDepositTxHex()); trader.setDepositTxKey(request.getDepositTxKey()); if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey()); @@ -130,7 +131,9 @@ public class ArbitratorProcessDepositRequest extends TradeTask { UUID.randomUUID().toString(), Version.getP2PMessageVersion(), new Date().getTime(), - null); + null, + trade.getBuyer().getSecurityDeposit().longValue(), + trade.getSeller().getSecurityDeposit().longValue()); // send deposit response to maker and taker sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response); @@ -158,7 +161,9 @@ public class ArbitratorProcessDepositRequest extends TradeTask { UUID.randomUUID().toString(), Version.getP2PMessageVersion(), new Date().getTime(), - t.getMessage()); + t.getMessage(), + trade.getBuyer().getSecurityDeposit().longValue(), + trade.getSeller().getSecurityDeposit().longValue()); // send deposit response to maker and taker sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java index 5ce230e2..69caf5e1 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java @@ -20,6 +20,7 @@ package haveno.core.trade.protocol.tasks; import haveno.common.app.Version; import haveno.common.taskrunner.TaskRunner; import haveno.core.trade.ArbitratorTrade; +import haveno.core.trade.BuyerTrade; import haveno.core.trade.HavenoUtils; import haveno.core.trade.MakerTrade; import haveno.core.trade.Trade; @@ -31,6 +32,7 @@ import lombok.extern.slf4j.Slf4j; import monero.daemon.model.MoneroOutput; import monero.wallet.model.MoneroTxWallet; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -91,10 +93,15 @@ public class MaybeSendSignContractRequest extends TradeTask { processModel.setDepositTxXmr(depositTx); // TODO: redundant with trade.getSelf().setDepositTx(), remove? trade.getSelf().setDepositTx(depositTx); trade.getSelf().setDepositTxHash(depositTx.getHash()); + trade.getSelf().setDepositTxFee(depositTx.getFee()); trade.getSelf().setReserveTxKeyImages(reservedKeyImages); trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address? trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId())); + // TODO: security deposit should be based on trade amount, not max offer amount + BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getOffer().getBuyerSecurityDeposit() : trade.getOffer().getSellerSecurityDeposit(); + trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee())); + // maker signs deposit hash nonce to avoid challenge protocol byte[] sig = null; if (trade instanceof MakerTrade) { diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositResponse.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositResponse.java index c210f488..4c18800a 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositResponse.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositResponse.java @@ -18,6 +18,8 @@ package haveno.core.trade.protocol.tasks; +import java.math.BigInteger; + import haveno.common.taskrunner.TaskRunner; import haveno.core.trade.Trade; import haveno.core.trade.messages.DepositResponse; @@ -43,10 +45,17 @@ public class ProcessDepositResponse extends TradeTask { throw new RuntimeException(message.getErrorMessage()); } + // record security deposits + trade.getBuyer().setSecurityDeposit(BigInteger.valueOf(message.getBuyerSecurityDeposit())); + trade.getSeller().setSecurityDeposit(BigInteger.valueOf(message.getSellerSecurityDeposit())); + // set success state trade.setStateIfValidTransitionTo(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS); trade.addInitProgressStep(); processModel.getTradeManager().requestPersistence(); + + // update balances + trade.getXmrWalletService().updateBalanceListeners(); complete(); } catch (Throwable t) { failed(t); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitTradeRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitTradeRequest.java index d369e68d..e1d4ed34 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitTradeRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitTradeRequest.java @@ -53,6 +53,10 @@ public class ProcessInitTradeRequest extends TradeTask { checkNotNull(request); checkTradeId(processModel.getOfferId(), request); + // validate inputs + if (trade.getAmount().compareTo(trade.getOffer().getAmount()) > 0) throw new RuntimeException("Trade amount exceeds offer amount"); + if (trade.getAmount().compareTo(trade.getOffer().getMinAmount()) < 0) throw new RuntimeException("Trade amount is less than minimum offer amount"); + // handle request as arbitrator TradePeer multisigParticipant; if (trade instanceof ArbitratorTrade) { diff --git a/core/src/main/java/haveno/core/xmr/Balances.java b/core/src/main/java/haveno/core/xmr/Balances.java index be607c02..721c2c81 100644 --- a/core/src/main/java/haveno/core/xmr/Balances.java +++ b/core/src/main/java/haveno/core/xmr/Balances.java @@ -17,28 +17,24 @@ package haveno.core.xmr; -import haveno.core.offer.OfferPayload; import haveno.core.offer.OpenOffer; import haveno.core.offer.OpenOfferManager; import haveno.core.support.dispute.Dispute; import haveno.core.support.dispute.refund.RefundManager; import haveno.core.trade.ClosedTradableManager; +import haveno.core.trade.MakerTrade; import haveno.core.trade.Trade; import haveno.core.trade.TradeManager; import haveno.core.trade.failed.FailedTradesManager; import haveno.core.xmr.listeners.XmrBalanceListener; import haveno.core.xmr.wallet.XmrWalletService; -import haveno.network.p2p.P2PService; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ListChangeListener; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import monero.common.MoneroError; import monero.wallet.model.MoneroOutputQuery; import monero.wallet.model.MoneroOutputWallet; -import monero.wallet.model.MoneroTxQuery; -import monero.wallet.model.MoneroTxWallet; import javax.inject.Inject; import java.math.BigInteger; @@ -77,19 +73,19 @@ public class Balances { } public void onAllServicesInitialized() { - openOfferManager.getObservableList().addListener((ListChangeListener) c -> updatedBalances()); - tradeManager.getObservableList().addListener((ListChangeListener) change -> updatedBalances()); - refundManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> updatedBalances()); + openOfferManager.getObservableList().addListener((ListChangeListener) c -> updateBalances()); + tradeManager.getObservableList().addListener((ListChangeListener) change -> updateBalances()); + refundManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> updateBalances()); xmrWalletService.addBalanceListener(new XmrBalanceListener() { @Override public void onBalanceChanged(BigInteger balance) { - updatedBalances(); + updateBalances(); } }); - updatedBalances(); + updateBalances(); } - private void updatedBalances() { + private void updateBalances() { if (!xmrWalletService.isWalletReady()) return; try { updateAvailableBalance(); @@ -111,7 +107,18 @@ public class Balances { private void updatePendingBalance() { BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.valueOf(0) : xmrWalletService.getWallet().getBalance(0); BigInteger unlockedBalance = xmrWalletService.getWallet() == null ? BigInteger.valueOf(0) : xmrWalletService.getWallet().getUnlockedBalance(0); - pendingBalance.set(balance.subtract(unlockedBalance)); + BigInteger pendingBalanceSum = balance.subtract(unlockedBalance); + + // add frozen trade balances - reserved amounts + List trades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList()); + for (Trade trade : trades) { + if (trade.getFrozenAmount().equals(new BigInteger("0"))) continue; + BigInteger tradeFee = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee(); + pendingBalanceSum = pendingBalanceSum.add(trade.getFrozenAmount()).subtract(trade.getReservedAmount()).subtract(tradeFee).subtract(trade.getSelf().getDepositTxFee()); + } + + // add frozen offer balances + pendingBalance.set(pendingBalanceSum); } private void updateReservedOfferBalance() { @@ -120,30 +127,21 @@ public class Balances { List frozenOutputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false)); for (MoneroOutputWallet frozenOutput : frozenOutputs) sum = sum.add(frozenOutput.getAmount()); } + + // subtract frozen trade balances + List trades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList()); + for (Trade trade : trades) { + sum = sum.subtract(trade.getFrozenAmount()); + } + reservedOfferBalance.set(sum); } private void updateReservedTradeBalance() { BigInteger sum = BigInteger.valueOf(0); - List openTrades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList()); - for (Trade trade : openTrades) { - try { - List depositTxs = xmrWalletService.getWallet().getTxs(new MoneroTxQuery() - .setHash(trade.getSelf().getDepositTxHash()) - .setInTxPool(false)); // don't check pool - if (depositTxs.size() != 1 || !depositTxs.get(0).isConfirmed()) continue; // outputs are frozen until confirmed by arbitrator's broadcast - } catch (MoneroError e) { - continue; - } - if (trade.getContract() == null) continue; - Long reservedAmt; - OfferPayload offerPayload = trade.getContract().getOfferPayload(); - if (trade.getArbitratorNodeAddress().equals(P2PService.getMyNodeAddress())) { // TODO (woodser): this only works if node address does not change - reservedAmt = offerPayload.getAmount() + offerPayload.getBuyerSecurityDeposit() + offerPayload.getSellerSecurityDeposit(); // arbitrator reserved balance is sum of amounts sent to multisig - } else { - reservedAmt = trade.getContract().isMyRoleBuyer(tradeManager.getKeyRing().getPubKeyRing()) ? offerPayload.getBuyerSecurityDeposit() : offerPayload.getAmount() + offerPayload.getSellerSecurityDeposit(); - } - sum = sum.add(BigInteger.valueOf(reservedAmt)); + List trades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList()); + for (Trade trade : trades) { + sum = sum.add(trade.getReservedAmount()); } reservedTradeBalance.set(sum); } diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java index 1bd7d535..e95e5474 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -368,7 +368,7 @@ public class XmrWalletService { return reserveTx; } - /**s + /** * Create the multisig deposit tx and freeze its inputs. * * @param trade the trade to create a deposit tx from @@ -388,8 +388,8 @@ public class XmrWalletService { Offer offer = trade.getProcessModel().getOffer(); String multisigAddress = trade.getProcessModel().getMultisigAddress(); BigInteger tradeFee = trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee(); - BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.valueOf(0) : offer.getAmount(); - BigInteger securityDeposit = trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit(); + BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.valueOf(0) : trade.getAmount(); + BigInteger securityDeposit = trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit(); // TODO: security deposit should be based on trade amount long time = System.currentTimeMillis(); log.info("Creating deposit tx with multisig address={}", multisigAddress); MoneroTxWallet depositTx = createTradeTx(tradeFee, sendAmount, securityDeposit, multisigAddress, reserveExactAmount, preferredSubaddressIndex); @@ -467,6 +467,7 @@ public class XmrWalletService { * Checks double spends, trade fee, deposit amount and destination, and miner fee. * The transaction is submitted to the pool then flushed without relaying. * + * @param offerId id of offer to verify trade tx * @param tradeFee trade fee * @param sendAmount amount to give peer * @param securityDeposit security deposit amount @@ -876,25 +877,6 @@ public class XmrWalletService { } } - private void notifyBalanceListeners() { - for (XmrBalanceListener balanceListener : balanceListeners) { - BigInteger balance; - if (balanceListener.getSubaddressIndex() != null && balanceListener.getSubaddressIndex() != 0) balance = getBalanceForSubaddress(balanceListener.getSubaddressIndex()); - else balance = getAvailableBalance(); - UserThread.execute(new Runnable() { // TODO (woodser): don't execute on UserThread - @Override - public void run() { - try { - balanceListener.onBalanceChanged(balance); - } catch (Exception e) { - log.warn("Failed to notify balance listener of change"); - e.printStackTrace(); - } - } - }); - } - } - private void changeWalletPasswords(String oldPassword, String newPassword) { // create task to change main wallet password @@ -1195,6 +1177,25 @@ public class XmrWalletService { balanceListeners.remove(listener); } + public void updateBalanceListeners() { + for (XmrBalanceListener balanceListener : balanceListeners) { + BigInteger balance; + if (balanceListener.getSubaddressIndex() != null && balanceListener.getSubaddressIndex() != 0) balance = getBalanceForSubaddress(balanceListener.getSubaddressIndex()); + else balance = getAvailableBalance(); + UserThread.execute(new Runnable() { // TODO (woodser): don't execute on UserThread + @Override + public void run() { + try { + balanceListener.onBalanceChanged(balance); + } catch (Exception e) { + log.warn("Failed to notify balance listener of change"); + e.printStackTrace(); + } + } + }); + } + } + public void saveAddressEntryList() { xmrAddressEntryList.requestPersistence(); } @@ -1259,7 +1260,7 @@ public class XmrWalletService { @Override public void run() { for (MoneroWalletListenerI listener : walletListeners) listener.onBalancesChanged(newBalance, newUnlockedBalance); - notifyBalanceListeners(); + updateBalanceListeners(); } }); } diff --git a/core/src/test/java/haveno/core/account/witness/AccountAgeWitnessServiceTest.java b/core/src/test/java/haveno/core/account/witness/AccountAgeWitnessServiceTest.java index 2dd02794..ba4acc9b 100644 --- a/core/src/test/java/haveno/core/account/witness/AccountAgeWitnessServiceTest.java +++ b/core/src/test/java/haveno/core/account/witness/AccountAgeWitnessServiceTest.java @@ -192,6 +192,7 @@ public class AccountAgeWitnessServiceTest { 1, DisputeResult.Winner.BUYER, DisputeResult.Reason.OTHER.ordinal(), + DisputeResult.SubtractFeeFrom.BUYER_ONLY, true, true, true, diff --git a/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java index 96efed5a..3c38a1c0 100644 --- a/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -216,6 +216,7 @@ public class DisputeSummaryWindow extends Overlay { disputeResult.setWinner(peersDisputeResult.getWinner()); disputeResult.setReason(peersDisputeResult.getReason()); disputeResult.setSummaryNotes(peersDisputeResult.summaryNotesProperty().get()); + disputeResult.setSubtractFeeFrom(peersDisputeResult.getSubtractFeeFrom()); buyerGetsTradeAmountRadioButton.setDisable(true); buyerGetsAllRadioButton.setDisable(true); @@ -403,7 +404,7 @@ public class DisputeSummaryWindow extends Overlay { disputeResult.setBuyerPayoutAmount(buyerAmount); disputeResult.setSellerPayoutAmount(sellerAmount); - disputeResult.setWinner(buyerAmount.compareTo(sellerAmount) > 0 ? + disputeResult.setWinner(buyerAmount.compareTo(sellerAmount) > 0 ? // TODO: UI should allow selection of receiver of exact custom amount, otherwise defaulting to bigger receiver. could extend API to specify who pays payout tx fee: buyer, seller, or both DisputeResult.Winner.BUYER : DisputeResult.Winner.SELLER); } diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 7a21d02c..eceedfc4 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -541,6 +541,8 @@ message OfferInfo { string version_nr = 25; int32 protocol_version = 26; string arbitrator_signer = 27; + string split_output_tx_hash = 28; + uint64 split_output_tx_fee = 29 [jstype = JS_STRING]; } message AvailabilityResultWithDescription { diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 6221af7e..1c87cfd2 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -281,6 +281,8 @@ message DepositResponse { string uid = 2; int64 current_date = 3; string error_message = 4; + int64 buyerSecurityDeposit = 5; + int64 sellerSecurityDeposit = 6; } message DepositsConfirmedMessage { @@ -740,6 +742,12 @@ message DisputeResult { PEER_WAS_LATE = 12; } + enum SubtractFeeFrom { + BUYER_ONLY = 0; + SELLER_ONLY = 1; + BUYER_AND_SELLER = 2; + } + string trade_id = 1; int32 trader_id = 2; Winner winner = 3; @@ -752,9 +760,10 @@ message DisputeResult { bytes arbitrator_signature = 10; int64 buyer_payout_amount = 11; int64 seller_payout_amount = 12; - bytes arbitrator_pub_key = 13; - int64 close_date = 14; - bool is_loser_publisher = 15; + SubtractFeeFrom subtract_fee_from = 13; + bytes arbitrator_pub_key = 14; + int64 close_date = 15; + bool is_loser_publisher = 16; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -1374,12 +1383,13 @@ message OpenOffer { State state = 2; int64 trigger_price = 3; bool reserve_exact_amount = 4; - repeated string scheduled_tx_hashes = 5; - string scheduled_amount = 6; // BigInteger - string split_output_tx_hash = 7; - string reserve_tx_hash = 8; - string reserve_tx_hex = 9; - string reserve_tx_key = 10; + string split_output_tx_hash = 5; + int64 split_output_tx_fee = 6; + repeated string scheduled_tx_hashes = 7; + string scheduled_amount = 8; // BigInteger + string reserve_tx_hash = 9; + string reserve_tx_hex = 10; + string reserve_tx_key = 11; } message Tradable { @@ -1573,9 +1583,10 @@ message TradePeer { string deposit_tx_hash = 1008; string deposit_tx_hex = 1009; string deposit_tx_key = 1010; - int64 security_deposit = 1011; - string updated_multisig_hex = 1012; - bool deposits_confirmed_message_acked = 1013; + int64 deposit_tx_fee = 1011; + int64 security_deposit = 1012; + string updated_multisig_hex = 1013; + bool deposits_confirmed_message_acked = 1014; } ///////////////////////////////////////////////////////////////////////////////////////////